refactor: 어플리케이션 업무코드(DAP) 전면 전환에 따른 패키지 및 모듈명 대규모 리팩토링
This commit is contained in:
17
dap-gateway/Dockerfile
Normal file
17
dap-gateway/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY gradlew .
|
||||
COPY gradle gradle
|
||||
COPY build.gradle settings.gradle ./
|
||||
COPY dap-common dap-common
|
||||
COPY dap-gateway dap-gateway
|
||||
RUN chmod +x gradlew
|
||||
RUN ./gradlew :dap-gateway:build -x test
|
||||
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Asia/Seoul
|
||||
COPY --from=builder /app/dap-gateway/build/libs/*-SNAPSHOT.jar app.jar
|
||||
EXPOSE 8081
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
38
dap-gateway/build.gradle
Normal file
38
dap-gateway/build.gradle
Normal file
@@ -0,0 +1,38 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':dap-common')
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
|
||||
// Spring AI MCP Server
|
||||
implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc'
|
||||
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
|
||||
// MyBatis & DB
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
|
||||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
implementation 'p6spy:p6spy:3.9.1'
|
||||
|
||||
// MapStruct
|
||||
implementation "org.mapstruct:mapstruct:1.5.5.Final"
|
||||
annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0"
|
||||
annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final"
|
||||
|
||||
// implementation 'com.networknt:json-schema-validator:1.4.0' // Spring AI 내장 버전과 충돌 방지를 위해 주석 처리
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.biz.mcp.gateway", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
||||
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.biz.mcp.gateway", "io.shinhanlife.dap.common.mcp", "io.shinhanlife.dap.common.config"})
|
||||
@EnableCaching
|
||||
public class AxHubGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AxHubGatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.aop;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.aop
|
||||
* @className GatewayLoggingAspect
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class GatewayLoggingAspect {
|
||||
|
||||
// gateway의 controller 패키지 하위의 모든 클래스/메서드 실행 시 작동
|
||||
@Around("execution(* io.shinhanlife.dap.biz.mcp.gateway.presentation..*(..))")
|
||||
public Object logGatewayExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
String targetMethod = joinPoint.getSignature().toShortString();
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
|
||||
log.info(" [Gateway 송신 시작] Target: {}", targetMethod);
|
||||
stopWatch.start();
|
||||
|
||||
try {
|
||||
// 실제 대상 메서드(외부 통신) 실행
|
||||
Object result = joinPoint.proceed();
|
||||
return result;
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
long timeMillis = stopWatch.getTotalTimeMillis();
|
||||
|
||||
if (timeMillis > 3000) { // 3초 이상 걸리면 WARN 로그로 슬로우 통신 경고
|
||||
log.warn("⏱ [Gateway 슬로우 응답] Target: {} | 소요시간: {}ms", targetMethod, timeMillis);
|
||||
} else {
|
||||
log.info("⏹ [Gateway 송신 완료] Target: {} | 소요시간: {}ms", targetMethod, timeMillis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.audit;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContext;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
/**
|
||||
* MCP Tool 호출 이력을 감사 로그로 남기는 서비스입니다.
|
||||
*
|
||||
* 금융권 환경에서 누가, 어떤 Agent로, 어떤 Tool을 호출했는지 추적하기 위한 로그를 담당합니다.
|
||||
*/
|
||||
public class AuditLogService {
|
||||
private static final Logger audit = LoggerFactory.getLogger("MCP_AUDIT");
|
||||
private final McpGatewayProperties properties;
|
||||
private final SensitiveDataMasker masker;
|
||||
|
||||
public AuditLogService(McpGatewayProperties properties, SensitiveDataMasker masker) {
|
||||
this.properties = properties;
|
||||
this.masker = masker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool 실행 시작 시점에 요청자와 마스킹된 인자를 기록합니다.
|
||||
*/
|
||||
public void toolStarted(McpRequestContext context, String toolName, JsonNode arguments) {
|
||||
if (!properties.auditEnabled()) {
|
||||
return;
|
||||
}
|
||||
audit.info("event=tool_started requestId={} agentId={} userId={} clientAddress={} tool={} arguments={}",
|
||||
context.requestId(), context.agentId(), context.userId(), context.clientAddress(), toolName, masker.mask(arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool 실행 종료 시점에 성공 여부, 처리 시간, 오류 유형을 기록합니다.
|
||||
*/
|
||||
public void toolFinished(McpRequestContext context, String toolName, long elapsedMillis, boolean success, String errorCode) {
|
||||
if (!properties.auditEnabled()) {
|
||||
return;
|
||||
}
|
||||
audit.info("event=tool_finished requestId={} agentId={} userId={} clientAddress={} tool={} elapsedMillis={} success={} errorCode={}",
|
||||
context.requestId(), context.agentId(), context.userId(), context.clientAddress(), toolName, elapsedMillis, success, errorCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mcp.agent-response")
|
||||
public class AgentResponseBudgetProperties {
|
||||
private Integer maxPreviewItems;
|
||||
private Integer maxFieldsPerItem;
|
||||
private Long maxItemBytes;
|
||||
private Long maxTotalBytes;
|
||||
private Boolean includeSummary;
|
||||
|
||||
public void setMaxPreviewItems(Integer maxPreviewItems) { this.maxPreviewItems = maxPreviewItems; }
|
||||
public void setMaxFieldsPerItem(Integer maxFieldsPerItem) { this.maxFieldsPerItem = maxFieldsPerItem; }
|
||||
public void setMaxItemBytes(Long maxItemBytes) { this.maxItemBytes = maxItemBytes; }
|
||||
public void setMaxTotalBytes(Long maxTotalBytes) { this.maxTotalBytes = maxTotalBytes; }
|
||||
public void setIncludeSummary(Boolean includeSummary) { this.includeSummary = includeSummary; }
|
||||
|
||||
public int maxPreviewItems() { return maxPreviewItems == null || maxPreviewItems < 1 ? 20 : maxPreviewItems; }
|
||||
public int maxFieldsPerItem() { return maxFieldsPerItem == null || maxFieldsPerItem < 1 ? 10 : maxFieldsPerItem; }
|
||||
public long maxItemBytes() { return maxItemBytes == null || maxItemBytes < 1 ? 4_096 : maxItemBytes; }
|
||||
public long maxTotalBytes() { return maxTotalBytes == null || maxTotalBytes < 1 ? 65_536 : maxTotalBytes; }
|
||||
public boolean includeSummary() { return includeSummary == null || includeSummary; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.config
|
||||
* @className GatewayFallbackProperties
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "mcp.gateway.fallback")
|
||||
public class GatewayFallbackProperties {
|
||||
private Map<String, String> routes = new HashMap<>();
|
||||
private String defaultUrl;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mcp")
|
||||
/**
|
||||
* application.properties의 mcp.* 설정을 Java 코드에서 사용하기 위한 설정 클래스입니다.
|
||||
*
|
||||
* API Key, 허용 Tool, 감사 로그, Redis Trace, Retry, Circuit Breaker,
|
||||
* EIMS/Tool 서버 주소 같은 MCP Gateway 운영 설정을 한 곳에서 관리합니다.
|
||||
*/
|
||||
public class McpGatewayProperties {
|
||||
private String apiKey;
|
||||
private List<String> allowedTools;
|
||||
private Boolean auditEnabled;
|
||||
private Boolean agentClaimsRequired;
|
||||
private Boolean trustedClaimsRequired;
|
||||
private Boolean writeApprovalRequired;
|
||||
private Boolean redisTraceEnabled;
|
||||
private Long redisTraceTtlSeconds;
|
||||
private Long toolTimeoutMillis;
|
||||
private Integer retryMaxAttempts;
|
||||
private Long retryInitialBackoffMillis;
|
||||
private Double retryBackoffMultiplier;
|
||||
private Long retryMaxBackoffMillis;
|
||||
private Integer circuitBreakerFailureThreshold;
|
||||
private Long circuitBreakerOpenMillis;
|
||||
private String eimsBaseUrl;
|
||||
private Long eimsTimeoutMillis;
|
||||
private String toolServerBaseUrl;
|
||||
private String toolServerApiKey;
|
||||
private Long toolHeartbeatTimeoutSeconds;
|
||||
private Integer defaultPageSize;
|
||||
private Integer maxPageSize;
|
||||
private Integer maxPagesPerCall;
|
||||
private Long maxStreamBytes;
|
||||
private Long maxDurationSeconds;
|
||||
private Long maxResponseBytesFromTool;
|
||||
private Long largeResponseMaxItemBytes;
|
||||
private Integer largeResponseMaxPreviewItems;
|
||||
private Integer toolExecutorCorePoolSize;
|
||||
private Integer toolExecutorMaxPoolSize;
|
||||
private Integer toolExecutorQueueCapacity;
|
||||
private Long toolExecutorKeepAliveSeconds;
|
||||
|
||||
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
|
||||
public void setAllowedTools(List<String> allowedTools) { this.allowedTools = allowedTools; }
|
||||
public void setAuditEnabled(Boolean auditEnabled) { this.auditEnabled = auditEnabled; }
|
||||
public void setAgentClaimsRequired(Boolean agentClaimsRequired) { this.agentClaimsRequired = agentClaimsRequired; }
|
||||
public void setTrustedClaimsRequired(Boolean trustedClaimsRequired) { this.trustedClaimsRequired = trustedClaimsRequired; }
|
||||
public void setWriteApprovalRequired(Boolean writeApprovalRequired) { this.writeApprovalRequired = writeApprovalRequired; }
|
||||
public void setRedisTraceEnabled(Boolean redisTraceEnabled) { this.redisTraceEnabled = redisTraceEnabled; }
|
||||
public void setRedisTraceTtlSeconds(Long redisTraceTtlSeconds) { this.redisTraceTtlSeconds = redisTraceTtlSeconds; }
|
||||
public void setToolTimeoutMillis(Long toolTimeoutMillis) { this.toolTimeoutMillis = toolTimeoutMillis; }
|
||||
public void setRetryMaxAttempts(Integer retryMaxAttempts) { this.retryMaxAttempts = retryMaxAttempts; }
|
||||
public void setRetryInitialBackoffMillis(Long retryInitialBackoffMillis) { this.retryInitialBackoffMillis = retryInitialBackoffMillis; }
|
||||
public void setRetryBackoffMultiplier(Double retryBackoffMultiplier) { this.retryBackoffMultiplier = retryBackoffMultiplier; }
|
||||
public void setRetryMaxBackoffMillis(Long retryMaxBackoffMillis) { this.retryMaxBackoffMillis = retryMaxBackoffMillis; }
|
||||
public void setCircuitBreakerFailureThreshold(Integer circuitBreakerFailureThreshold) { this.circuitBreakerFailureThreshold = circuitBreakerFailureThreshold; }
|
||||
public void setCircuitBreakerOpenMillis(Long circuitBreakerOpenMillis) { this.circuitBreakerOpenMillis = circuitBreakerOpenMillis; }
|
||||
public void setEimsBaseUrl(String eimsBaseUrl) { this.eimsBaseUrl = eimsBaseUrl; }
|
||||
public void setEimsTimeoutMillis(Long eimsTimeoutMillis) { this.eimsTimeoutMillis = eimsTimeoutMillis; }
|
||||
public void setToolServerBaseUrl(String toolServerBaseUrl) { this.toolServerBaseUrl = toolServerBaseUrl; }
|
||||
public void setToolServerApiKey(String toolServerApiKey) { this.toolServerApiKey = toolServerApiKey; }
|
||||
public void setToolHeartbeatTimeoutSeconds(Long toolHeartbeatTimeoutSeconds) { this.toolHeartbeatTimeoutSeconds = toolHeartbeatTimeoutSeconds; }
|
||||
public void setDefaultPageSize(Integer defaultPageSize) { this.defaultPageSize = defaultPageSize; }
|
||||
public void setMaxPageSize(Integer maxPageSize) { this.maxPageSize = maxPageSize; }
|
||||
public void setMaxPagesPerCall(Integer maxPagesPerCall) { this.maxPagesPerCall = maxPagesPerCall; }
|
||||
public void setMaxStreamBytes(Long maxStreamBytes) { this.maxStreamBytes = maxStreamBytes; }
|
||||
public void setMaxDurationSeconds(Long maxDurationSeconds) { this.maxDurationSeconds = maxDurationSeconds; }
|
||||
public void setMaxResponseBytesFromTool(Long maxResponseBytesFromTool) { this.maxResponseBytesFromTool = maxResponseBytesFromTool; }
|
||||
public void setLargeResponseMaxItemBytes(Long largeResponseMaxItemBytes) { this.largeResponseMaxItemBytes = largeResponseMaxItemBytes; }
|
||||
public void setLargeResponseMaxPreviewItems(Integer largeResponseMaxPreviewItems) { this.largeResponseMaxPreviewItems = largeResponseMaxPreviewItems; }
|
||||
public void setToolExecutorCorePoolSize(Integer toolExecutorCorePoolSize) { this.toolExecutorCorePoolSize = toolExecutorCorePoolSize; }
|
||||
public void setToolExecutorMaxPoolSize(Integer toolExecutorMaxPoolSize) { this.toolExecutorMaxPoolSize = toolExecutorMaxPoolSize; }
|
||||
public void setToolExecutorQueueCapacity(Integer toolExecutorQueueCapacity) { this.toolExecutorQueueCapacity = toolExecutorQueueCapacity; }
|
||||
public void setToolExecutorKeepAliveSeconds(Long toolExecutorKeepAliveSeconds) { this.toolExecutorKeepAliveSeconds = toolExecutorKeepAliveSeconds; }
|
||||
|
||||
public String apiKey() { return apiKey; }
|
||||
public boolean apiKeyEnabled() { return apiKey != null && !apiKey.isBlank(); }
|
||||
public boolean auditEnabled() { return auditEnabled == null || auditEnabled; }
|
||||
public boolean agentClaimsRequired() { return agentClaimsRequired == null || agentClaimsRequired; }
|
||||
public boolean trustedClaimsRequired() { return trustedClaimsRequired == null || trustedClaimsRequired; }
|
||||
public boolean writeApprovalRequired() { return writeApprovalRequired == null || writeApprovalRequired; }
|
||||
public boolean redisTraceEnabled() { return redisTraceEnabled == null || redisTraceEnabled; }
|
||||
public long redisTraceTtlSeconds() { return redisTraceTtlSeconds == null || redisTraceTtlSeconds < 1 ? 3_600 : redisTraceTtlSeconds; }
|
||||
public long toolTimeoutMillis() { return toolTimeoutMillis == null || toolTimeoutMillis < 1 ? 5_000 : toolTimeoutMillis; }
|
||||
public int retryMaxAttempts() { return retryMaxAttempts == null || retryMaxAttempts < 1 ? 3 : retryMaxAttempts; }
|
||||
public long retryInitialBackoffMillis() { return retryInitialBackoffMillis == null || retryInitialBackoffMillis < 1 ? 1_000 : retryInitialBackoffMillis; }
|
||||
public double retryBackoffMultiplier() { return retryBackoffMultiplier == null || retryBackoffMultiplier < 1.0 ? 2.0 : retryBackoffMultiplier; }
|
||||
public long retryMaxBackoffMillis() { return retryMaxBackoffMillis == null || retryMaxBackoffMillis < 1 ? 5_000 : retryMaxBackoffMillis; }
|
||||
public int circuitBreakerFailureThreshold() { return circuitBreakerFailureThreshold == null || circuitBreakerFailureThreshold < 1 ? 5 : circuitBreakerFailureThreshold; }
|
||||
public long circuitBreakerOpenMillis() { return circuitBreakerOpenMillis == null || circuitBreakerOpenMillis < 1 ? 30_000 : circuitBreakerOpenMillis; }
|
||||
public String eimsBaseUrl() { return eimsBaseUrl == null ? "" : eimsBaseUrl.trim(); }
|
||||
public long eimsTimeoutMillis() { return eimsTimeoutMillis == null || eimsTimeoutMillis < 1 ? 3_000 : eimsTimeoutMillis; }
|
||||
public String toolServerBaseUrl() { return toolServerBaseUrl == null || toolServerBaseUrl.isBlank() ? "http://localhost:9090" : toolServerBaseUrl.trim(); }
|
||||
public String toolServerApiKey() { return toolServerApiKey == null ? "" : toolServerApiKey.trim(); }
|
||||
public long toolHeartbeatTimeoutSeconds() { return toolHeartbeatTimeoutSeconds == null || toolHeartbeatTimeoutSeconds < 1 ? 30 : toolHeartbeatTimeoutSeconds; }
|
||||
public int defaultPageSize() { return defaultPageSize == null || defaultPageSize < 1 ? 100 : defaultPageSize; }
|
||||
public int maxPageSize() { return maxPageSize == null || maxPageSize < 1 ? 500 : maxPageSize; }
|
||||
public int maxPagesPerCall() { return maxPagesPerCall == null || maxPagesPerCall < 1 ? 3 : maxPagesPerCall; }
|
||||
public long maxStreamBytes() { return maxStreamBytes == null || maxStreamBytes < 1 ? 1_048_576 : maxStreamBytes; }
|
||||
public long maxDurationSeconds() { return maxDurationSeconds == null || maxDurationSeconds < 1 ? 10 : maxDurationSeconds; }
|
||||
public long maxResponseBytesFromTool() { return maxResponseBytesFromTool == null || maxResponseBytesFromTool < 1 ? 5_242_880 : maxResponseBytesFromTool; }
|
||||
public long largeResponseMaxItemBytes() { return largeResponseMaxItemBytes == null || largeResponseMaxItemBytes < 1 ? 16_384 : largeResponseMaxItemBytes; }
|
||||
public int largeResponseMaxPreviewItems() { return largeResponseMaxPreviewItems == null || largeResponseMaxPreviewItems < 1 ? 100 : largeResponseMaxPreviewItems; }
|
||||
public int toolExecutorCorePoolSize() { return toolExecutorCorePoolSize == null || toolExecutorCorePoolSize < 1 ? 20 : toolExecutorCorePoolSize; }
|
||||
public int toolExecutorMaxPoolSize() {
|
||||
int core = toolExecutorCorePoolSize();
|
||||
return toolExecutorMaxPoolSize == null || toolExecutorMaxPoolSize < core ? Math.max(core, 100) : toolExecutorMaxPoolSize;
|
||||
}
|
||||
public int toolExecutorQueueCapacity() { return toolExecutorQueueCapacity == null || toolExecutorQueueCapacity < 1 ? 200 : toolExecutorQueueCapacity; }
|
||||
public long toolExecutorKeepAliveSeconds() { return toolExecutorKeepAliveSeconds == null || toolExecutorKeepAliveSeconds < 1 ? 60 : toolExecutorKeepAliveSeconds; }
|
||||
|
||||
public Set<String> allowedToolSet() {
|
||||
if (allowedTools == null) {
|
||||
return Set.of();
|
||||
}
|
||||
return allowedTools.stream()
|
||||
.filter(tool -> tool != null && !tool.isBlank())
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.config;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.config
|
||||
* @className RedisConfig
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, ToolMetadata> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, ToolMetadata> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 1. Key는 무조건 String
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// 2. Value는 Generic 직렬화기 사용 (생성자 인자 없음)
|
||||
Jackson2JsonRedisSerializer<ToolMetadata> serializer = new Jackson2JsonRedisSerializer<>(ToolMetadata.class);
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.dto;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.dto
|
||||
* @className OperationType
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public enum OperationType {
|
||||
READ,
|
||||
WRITE
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
/**
|
||||
* Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스
|
||||
* Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다.
|
||||
*/
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.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초당 허용 최대 요청 수
|
||||
|
||||
// 7. Gateway 코어 제어용 설정 필드 추가 (재시도, 타임아웃, 오퍼레이션 타입)
|
||||
@Builder.Default
|
||||
private OperationType operationType = OperationType.READ;
|
||||
|
||||
@Builder.Default
|
||||
private Boolean retryEnabled = true;
|
||||
|
||||
@Builder.Default
|
||||
private Integer circuitBreakerFailureThreshold = 0;
|
||||
|
||||
@Builder.Default
|
||||
private Long circuitBreakerOpenMillis = 0L;
|
||||
|
||||
@Builder.Default
|
||||
private Long timeoutMillis = 0L;
|
||||
|
||||
// --- Guardrail 호환성을 위한 메서드 추가 ---
|
||||
public Set<String> allowedArguments() {
|
||||
if (parametersSchema == null || !parametersSchema.containsKey("properties")) return Set.of();
|
||||
return ((Map<String, Object>) parametersSchema.get("properties")).keySet();
|
||||
}
|
||||
|
||||
public Set<String> requiredArguments() {
|
||||
if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of();
|
||||
return new HashSet<>((List<String>) parametersSchema.get("required"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.guardrail;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
@Service
|
||||
/**
|
||||
* Tool 호출 인자의 규격을 검증하는 Guardrail 서비스입니다.
|
||||
*
|
||||
* 필수값 누락, 허용되지 않은 인자 전달 같은 문제를 Tool 서버 호출 전에 차단합니다.
|
||||
*/
|
||||
public class GuardrailService {
|
||||
/**
|
||||
* ToolMetadata에 정의된 required/allowed arguments 기준으로 요청 인자를 검증합니다.
|
||||
*/
|
||||
public void validate(ToolMetadata metadata, ObjectNode arguments) {
|
||||
if (metadata.getParametersSchema() == null) {
|
||||
// 스키마 정보가 없으면(Fallback 툴 등) 검증을 생략합니다.
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<String> fieldNames = arguments.fieldNames();
|
||||
while (fieldNames.hasNext()) {
|
||||
String fieldName = fieldNames.next();
|
||||
if (!metadata.allowedArguments().contains(fieldName)) {
|
||||
throw new ToolExecutionException(FailureType.CLIENT_ERROR, "Unknown argument for " + metadata.getName() + ": " + fieldName);
|
||||
}
|
||||
}
|
||||
for (String requiredArgument : metadata.requiredArguments()) {
|
||||
JsonNode value = arguments.get(requiredArgument);
|
||||
if (isMissing(value)) {
|
||||
throw new ToolExecutionException(FailureType.CLIENT_ERROR, "Missing required argument for " + metadata.getName() + ": " + requiredArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMissing(JsonNode value) {
|
||||
if (value == null || value.isNull() || value.isMissingNode()) {
|
||||
return true;
|
||||
}
|
||||
return value.isTextual() && value.asText("").isBlank();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.guardrail;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Audit Log, Redis Trace, Agent 응답 preview에 남으면 안 되는 민감정보를 마스킹합니다.
|
||||
*/
|
||||
@Component
|
||||
public class SensitiveDataMasker {
|
||||
private static final Set<String> SENSITIVE_KEYS = Set.of(
|
||||
"password", "passwd", "pwd", "token", "accessToken", "refreshToken", "secret",
|
||||
"ssn", "rrn", "residentNumber", "cardNumber", "accountNumber", "accountNo",
|
||||
"phone", "mobile", "email", "idempotencyKey");
|
||||
private static final Pattern EMAIL = Pattern.compile("([a-zA-Z0-9._%+-]{2})[a-zA-Z0-9._%+-]*(@[a-zA-Z0-9.-]+)");
|
||||
private static final Pattern CARD_OR_ACCOUNT = Pattern.compile("\\b(\\d{4})\\d{4,12}(\\d{2,4})\\b");
|
||||
private final ObjectMapper json;
|
||||
|
||||
public SensitiveDataMasker(ObjectMapper json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* JsonNode 전체를 재귀적으로 순회하며 민감 key와 민감 패턴을 마스킹합니다.
|
||||
*/
|
||||
public JsonNode mask(JsonNode input) {
|
||||
if (input == null || input.isMissingNode() || input.isNull()) {
|
||||
return json.createObjectNode();
|
||||
}
|
||||
if (input.isArray()) {
|
||||
ArrayNode masked = json.createArrayNode();
|
||||
for (JsonNode item : input) {
|
||||
masked.add(mask(item));
|
||||
}
|
||||
return masked;
|
||||
}
|
||||
if (input.isObject()) {
|
||||
ObjectNode masked = json.createObjectNode();
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = input.fields();
|
||||
while (fields.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = fields.next();
|
||||
String key = entry.getKey();
|
||||
JsonNode value = entry.getValue();
|
||||
if (isSensitiveKey(key)) {
|
||||
masked.put(key, "***");
|
||||
} else {
|
||||
masked.set(key, mask(value));
|
||||
}
|
||||
}
|
||||
return masked;
|
||||
}
|
||||
if (input.isTextual()) {
|
||||
return json.valueToTree(maskText(input.asText()));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
private boolean isSensitiveKey(String key) {
|
||||
return key != null && SENSITIVE_KEYS.stream().anyMatch(sensitive -> sensitive.equalsIgnoreCase(key));
|
||||
}
|
||||
|
||||
private String maskText(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return value;
|
||||
}
|
||||
String masked = EMAIL.matcher(value).replaceAll("$1***$2");
|
||||
return CARD_OR_ACCOUNT.matcher(masked).replaceAll("$1********$2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.guardrail;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* Tool 서버 응답을 Agent에게 전달하기 전에 환각 방어 규칙을 적용하는 서비스입니다.
|
||||
*/
|
||||
@Service
|
||||
public class ToolResponseGuardrailService {
|
||||
private final ObjectMapper json;
|
||||
|
||||
public ToolResponseGuardrailService(ObjectMapper json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool 응답 data를 검증한 뒤 표준 MCP 응답 문자열로 감쌉니다.
|
||||
*/
|
||||
public String validateAndWrap(ToolMetadata metadata, String requestId, JsonNode data) {
|
||||
validateData(metadata.getName(), data);
|
||||
|
||||
ObjectNode response;
|
||||
if (data.isObject() && data.has("success") && data.has("data")) {
|
||||
response = ((ObjectNode) data).deepCopy();
|
||||
response.set("answerPolicy", answerPolicy());
|
||||
} else {
|
||||
response = json.createObjectNode();
|
||||
response.put("success", true);
|
||||
response.put("toolName", metadata.getName());
|
||||
response.put("requestId", requestId);
|
||||
response.put("source", "registered-tool-server");
|
||||
response.set("data", data);
|
||||
response.set("answerPolicy", answerPolicy());
|
||||
}
|
||||
|
||||
try {
|
||||
return json.writerWithDefaultPrettyPrinter().writeValueAsString(response);
|
||||
} catch (Exception error) {
|
||||
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "검증된 Tool 응답 직렬화에 실패했습니다.", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool 응답 data를 Agent에게 전달하기 전에 응답 규격으로 검증합니다.
|
||||
*/
|
||||
public void validateData(String toolName, JsonNode data) {
|
||||
if (data == null || data.isMissingNode() || data.isNull()) {
|
||||
throw new ToolExecutionException(FailureType.HALLUCINATION_GUARDRAIL, "Tool 응답 data가 비어 있습니다. tool=" + toolName);
|
||||
}
|
||||
// 향후 ToolMetadata에 responseAllowedFields 등이 추가되면 스키마 검증 로직 추가
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent가 Tool 결과 밖의 내용을 추측하지 않도록 응답 정책을 함께 내려줍니다.
|
||||
*/
|
||||
private ObjectNode answerPolicy() {
|
||||
ObjectNode policy = json.createObjectNode();
|
||||
policy.put("allowOnlyToolData", true);
|
||||
policy.put("noGuessing", true);
|
||||
policy.put("onMissingData", "answer_unknown_or_request_more_information");
|
||||
policy.put("instruction", "Tool이 반환한 data 필드만 사용하세요. data에 없는 값은 추측해서 만들지 마세요.");
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.messaging;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.messaging
|
||||
* @className KafkaProducerService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KafkaProducerService {
|
||||
|
||||
// Spring Kafka 제공 템플릿
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
private final ObjectMapper jsonMapper;
|
||||
|
||||
/**
|
||||
* 트래픽 초과 등의 이유로 즉시 처리가 불가능한 요청을 Kafka 대기열로 보냅니다.
|
||||
* @param topic 발행할 Kafka 토픽명
|
||||
* @param payload 원본 요청 페이로드
|
||||
* @return 발급된 고유 티켓 ID
|
||||
*/
|
||||
public String queueRequest(String topic, Map<String, Object> payload) {
|
||||
String ticketId = "TICKET-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
|
||||
|
||||
// 페이로드에 티켓 ID 추가 (Consumer가 알 수 있도록)
|
||||
payload.put("ticketId", ticketId);
|
||||
payload.put("queuedAt", System.currentTimeMillis());
|
||||
|
||||
try {
|
||||
String jsonPayload = jsonMapper.writeValueAsString(payload);
|
||||
// 실제 Kafka 서버로 전송
|
||||
kafkaTemplate.send(topic, ticketId, jsonPayload);
|
||||
log.info(" [Kafka Queue] 요청 대기열 등록 완료 - Topic: {}, Ticket ID: {}", topic, ticketId);
|
||||
} catch (Exception e) {
|
||||
// 로컬 테스트 시 실제 Kafka 브로커가 없어서 나는 예외를 방어합니다.
|
||||
// 실제 상용 환경에서는 Dead Letter Queue 등을 태우거나 예외 처리 정책에 따릅니다.
|
||||
log.error(" [Kafka Queue] Kafka 서버 통신 실패 (Broker Down). Mock 모드로 티켓은 발급합니다. Error: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return ticketId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.service.ExecuteService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/chat")
|
||||
@RequiredArgsConstructor
|
||||
public class ChatController {
|
||||
|
||||
private final ExecuteService executeService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RedisRegistryService registryService;
|
||||
private final ChatClient.Builder chatClientBuilder;
|
||||
|
||||
@PostMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter chatStream(@RequestBody Map<String, String> request,
|
||||
@RequestHeader(value = "X-Agent-Id", required = false) String agentId,
|
||||
@RequestHeader(value = "X-Tenant-Id", required = false, defaultValue = "system") String tenantId) {
|
||||
String effectiveTenantId = (agentId != null && !agentId.trim().isEmpty()) ? agentId : tenantId;
|
||||
String message = request.getOrDefault("message", "").trim();
|
||||
log.info("[Real AI Chat] 사용자의 메시지 수신: {}", message);
|
||||
|
||||
SseEmitter emitter = new SseEmitter(120000L); // 120 seconds timeout
|
||||
|
||||
try {
|
||||
List<ToolCallback> callbacks = new ArrayList<>();
|
||||
for (ToolMetadata meta : registryService.getAllTools()) {
|
||||
if (Boolean.TRUE.equals(meta.getVisible())) {
|
||||
callbacks.add(new DynamicMcpToolCallback(meta, executeService, objectMapper, effectiveTenantId));
|
||||
}
|
||||
}
|
||||
|
||||
ChatClient chatClient = chatClientBuilder
|
||||
.defaultSystem("You are AX HUB Assistant, a highly capable enterprise AI agent. You must use the provided tools to answer user questions when necessary. Always answer politely in Korean.")
|
||||
.build();
|
||||
|
||||
Flux<String> responseStream = chatClient.prompt()
|
||||
.user(message)
|
||||
.tools((Object[]) callbacks.toArray(new ToolCallback[0])) // Spring AI 2.0 uses tools()
|
||||
.stream()
|
||||
.content();
|
||||
|
||||
responseStream.subscribe(
|
||||
chunk -> {
|
||||
try {
|
||||
if (chunk != null) {
|
||||
emitter.send(chunk);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
},
|
||||
error -> {
|
||||
log.error("[Real AI Chat] 스트리밍 중 에러 발생", error);
|
||||
try {
|
||||
String errorMsg = error.getMessage();
|
||||
if (errorMsg != null && (errorMsg.contains("timeout") || errorMsg.contains("OpenAIIoException"))) {
|
||||
emitter.send("\n\n⚠️ **요청 시간이 초과되었습니다.** (현재 여러 개의 도구를 분석하느라 모델의 응답이 지연되었습니다. 해당하는 도구가 없거나 너무 복잡한 요청일 수 있습니다.)");
|
||||
} else if (errorMsg != null && errorMsg.contains("503")) {
|
||||
emitter.send("\n\n⚠️ **AI 모델 서버 혼잡 (503)**: 현재 AI 모델을 제공하는 서버에 일시적으로 접속자가 많아 지연이 발생하고 있습니다. 잠시 후 다시 시도해 주세요.");
|
||||
} else if (errorMsg != null && errorMsg.contains("429")) {
|
||||
emitter.send("\n\n⚠️ **API 사용량 초과 (429)**: 현재 사용 중인 Gemini API(무료 티어)의 일일 또는 분당 요청 한도를 초과했습니다. 잠시 후 다시 시도하시거나 API 플랜을 확인해 주세요.");
|
||||
} else {
|
||||
emitter.send("\n[에러 발생: " + errorMsg + "]");
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
emitter.complete(); // 클라이언트 측에서 네트워크 에러로 처리하지 않도록 정상 종료
|
||||
},
|
||||
() -> {
|
||||
log.info("[Real AI Chat] 스트리밍 완료");
|
||||
emitter.complete();
|
||||
}
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[Real AI Chat] 초기화 중 에러 발생", e);
|
||||
try {
|
||||
emitter.send("초기화 중 에러가 발생했습니다: " + e.getMessage());
|
||||
} catch (Exception ignore) {}
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
private static class DynamicMcpToolCallback implements ToolCallback {
|
||||
private final ToolMetadata metadata;
|
||||
private final ExecuteService executeService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ToolDefinition toolDefinition;
|
||||
private final String tenantId;
|
||||
|
||||
public DynamicMcpToolCallback(ToolMetadata metadata, ExecuteService executeService, ObjectMapper objectMapper, String tenantId) {
|
||||
this.metadata = metadata;
|
||||
this.executeService = executeService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.tenantId = tenantId;
|
||||
|
||||
String schema = "{\"type\":\"object\",\"properties\":{}}";
|
||||
try {
|
||||
if (metadata.getParametersSchema() != null) {
|
||||
schema = objectMapper.writeValueAsString(metadata.getParametersSchema());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Schema parsing error for tool: {}", metadata.getName());
|
||||
}
|
||||
|
||||
this.toolDefinition = ToolDefinition.builder()
|
||||
.name(metadata.getName().replaceAll("[^a-zA-Z0-9_-]", "_"))
|
||||
.description(metadata.getDescription() != null ? metadata.getDescription() : "No description provided")
|
||||
.inputSchema(schema)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolDefinition getToolDefinition() {
|
||||
return this.toolDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String toolInput) {
|
||||
log.info("[Function Calling] LLM이 '{}' 툴을 호출했습니다. 입력값: {}", metadata.getName(), toolInput);
|
||||
try {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("jsonrpc", "2.0");
|
||||
payload.put("method", "tools/call");
|
||||
payload.put("id", UUID.randomUUID().toString());
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("name", metadata.getName());
|
||||
|
||||
if (toolInput != null && !toolInput.trim().isEmpty()) {
|
||||
Map<String, Object> arguments = objectMapper.readValue(toolInput, Map.class);
|
||||
params.put("arguments", arguments);
|
||||
} else {
|
||||
params.put("arguments", new HashMap<>());
|
||||
}
|
||||
|
||||
payload.put("params", params);
|
||||
|
||||
Object result = executeService.execute(payload, this.tenantId);
|
||||
String jsonResult = objectMapper.writeValueAsString(result);
|
||||
log.info("[Function Calling] '{}' 툴 실행 완료. 결과: {}", metadata.getName(), jsonResult);
|
||||
return jsonResult;
|
||||
} catch (Exception e) {
|
||||
log.error("[Function Calling] '{}' 툴 실행 중 에러", metadata.getName(), e);
|
||||
return "{\"error\": \"" + e.getMessage() + "\"}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.service.KillSwitchService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.presentation
|
||||
* @className KillSwitchController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/mcp/api/v1/admin/kill-switch")
|
||||
@RequiredArgsConstructor
|
||||
public class KillSwitchController {
|
||||
|
||||
private final KillSwitchService killSwitchService;
|
||||
|
||||
/**
|
||||
* 관리자가 비상 차단(Kill Switch) 상태를 토글합니다.
|
||||
* @param type "agent", "tool", "route" 중 하나
|
||||
* @param target 차단할 대상의 ID 또는 이름
|
||||
* @param state true(차단), false(차단 해제)
|
||||
*/
|
||||
@PostMapping("/{type}/{target}")
|
||||
public ResponseEntity<?> toggleKillSwitch(
|
||||
@PathVariable String type,
|
||||
@PathVariable String target,
|
||||
@RequestParam boolean state) {
|
||||
|
||||
killSwitchService.toggleKillSwitch(type, target, state);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"message", "Kill Switch 상태 변경 완료",
|
||||
"type", type,
|
||||
"target", target,
|
||||
"state", state
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자가 비상 차단(Kill Switch) 키를 Redis에서 완전히 삭제합니다.
|
||||
*/
|
||||
@DeleteMapping("/{type}/{target}")
|
||||
public ResponseEntity<?> removeKillSwitch(
|
||||
@PathVariable String type,
|
||||
@PathVariable String target) {
|
||||
|
||||
killSwitchService.removeKillSwitch(type, target);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"message", "Kill Switch 키 삭제 완료 (차단 해제)",
|
||||
"type", type,
|
||||
"target", target
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcRequest;
|
||||
import io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcResponse;
|
||||
import io.shinhanlife.dap.biz.mcp.adapter.dto.Params;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.GatewayFallbackProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.service.ExecuteService;
|
||||
import io.shinhanlife.dap.common.mcp.security.SecurityProperties;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/mcp/api/v1")
|
||||
@Tag(name = "MCP Router API", description = "AI Agent의 요청을 받아 Adapter 시스템으로 라우팅하는 게이트웨이 API")
|
||||
public class McpRouterController {
|
||||
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final ExecuteService executeService;
|
||||
private final SecurityProperties securityProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final GatewayFallbackProperties gatewayFallbackProperties;
|
||||
private final RestClient restClient;
|
||||
|
||||
public McpRouterController(RedisRegistryService redisRegistryService,
|
||||
ExecuteService executeService,
|
||||
SecurityProperties securityProperties,
|
||||
ObjectMapper objectMapper,
|
||||
GatewayFallbackProperties gatewayFallbackProperties) {
|
||||
this.redisRegistryService = redisRegistryService;
|
||||
this.executeService = executeService;
|
||||
this.securityProperties = securityProperties;
|
||||
this.objectMapper = objectMapper;
|
||||
this.gatewayFallbackProperties = gatewayFallbackProperties;
|
||||
|
||||
// [수정됨] 1초 타임아웃을 강제하여 죽은 서버 대기로 인한 지연 방지
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(1000); // 연결 시도 타임아웃 1초
|
||||
factory.setReadTimeout(1000); // 응답 대기 타임아웃 1초
|
||||
|
||||
this.restClient = RestClient.builder().requestFactory(factory).build();
|
||||
}
|
||||
|
||||
@PostMapping("/tools/call")
|
||||
public ResponseEntity<?> callTool(@RequestBody Map<String, Object> payload,
|
||||
@RequestHeader(value = "X-Agent-Id", required = false) String agentId,
|
||||
@RequestHeader(value = "X-Tenant-Id", required = false, defaultValue = "system") String tenantId) {
|
||||
|
||||
String effectiveTenantId = (agentId != null && !agentId.trim().isEmpty()) ? agentId : tenantId;
|
||||
|
||||
try {
|
||||
log.info("[Admin UI -> MCP Gateway] 동적 툴 실행 요청 수신: {}", objectMapper.writeValueAsString(payload));
|
||||
} catch (Exception ex) {
|
||||
log.info("[Admin UI -> MCP Gateway] 동적 툴 실행 요청 수신: {}", payload);
|
||||
}
|
||||
|
||||
try {
|
||||
Object result = executeService.execute(payload, effectiveTenantId);
|
||||
|
||||
io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcResponse response = new io.shinhanlife.dap.biz.mcp.adapter.dto.JsonRpcResponse();
|
||||
response.setJsonrpc("2.0");
|
||||
response.setId(payload.containsKey("id") ? String.valueOf(payload.get("id")) : UUID.randomUUID().toString());
|
||||
response.setResult(result);
|
||||
|
||||
try {
|
||||
log.info("[MCP Gateway -> Admin UI] 동적 툴 실행 결과 반환: {}", objectMapper.writeValueAsString(response));
|
||||
} catch (Exception ex) {
|
||||
log.info("[MCP Gateway -> Admin UI] 동적 툴 실행 결과 반환: {}", response);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (SecurityException se) {
|
||||
log.warn(" [보안 차단] 권한 오류: {}", se.getMessage());
|
||||
return ResponseEntity.status(403).body(Map.of("error", se.getMessage()));
|
||||
} catch (Exception e) {
|
||||
log.error(" 파이프라인 실행 중 오류:", e);
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/tools/list")
|
||||
public ResponseEntity<JsonRpcResponse> listTools(
|
||||
@RequestParam(value = "categoryKey", required = false) String categoryKey) {
|
||||
List<ToolMetadata> activeTools = redisRegistryService.getAllTools()
|
||||
.stream()
|
||||
.filter(ToolMetadata::getVisible)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Set<String> knownTools = activeTools.stream()
|
||||
.map(ToolMetadata::getUid)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> fallbackUrls = new HashSet<>(gatewayFallbackProperties.getRoutes().values());
|
||||
if (gatewayFallbackProperties.getDefaultUrl() != null) {
|
||||
fallbackUrls.add(gatewayFallbackProperties.getDefaultUrl());
|
||||
}
|
||||
|
||||
for (String url : fallbackUrls) {
|
||||
try {
|
||||
List<ToolMetadata> localTools = restClient.get()
|
||||
.uri(url + "/mcp/api/v1/tools/local")
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<ToolMetadata>>() {});
|
||||
|
||||
if (localTools != null) {
|
||||
for (ToolMetadata t : localTools) {
|
||||
if (!Boolean.TRUE.equals(t.getIsRegistered()) && Boolean.TRUE.equals(t.getVisible()) && !knownTools.contains(t.getUid())) {
|
||||
activeTools.add(t);
|
||||
knownTools.add(t.getUid());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to fetch local tools from fallback URL: {}", url);
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryKey != null && !categoryKey.trim().isEmpty()) {
|
||||
activeTools = activeTools.stream()
|
||||
.filter(t -> categoryKey.equals(t.getCategoryKey()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
JsonRpcResponse response = new JsonRpcResponse();
|
||||
response.setId(UUID.randomUUID().toString());
|
||||
response.setResult(Map.of("tools", activeTools));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
@GetMapping(value = "/tools/docs/markdown", produces = "text/markdown;charset=UTF-8")
|
||||
public ResponseEntity<String> generateToolsMarkdown() {
|
||||
List<ToolMetadata> tools = redisRegistryService.getAllTools()
|
||||
.stream()
|
||||
.filter(ToolMetadata::getVisible)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
StringBuilder md = new StringBuilder();
|
||||
md.append("# \uD83E\uDD16 Shinhan AI Tool Catalog\n\n");
|
||||
md.append("**총 등록된 툴:** ").append(tools.size()).append("개\n");
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
md.append("**마지막 업데이트:** ").append(LocalDateTime.now().format(formatter)).append("\n\n");
|
||||
md.append("---\n\n");
|
||||
|
||||
// Group by Domain Group
|
||||
Map<String, List<ToolMetadata>> groupedTools = new HashMap<>();
|
||||
for (ToolMetadata tool : tools) {
|
||||
String group = tool.getCategoryKey() != null ? tool.getCategoryKey() : "기타 (Others)";
|
||||
groupedTools.computeIfAbsent(group, k -> new ArrayList<>()).add(tool);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<ToolMetadata>> entry : groupedTools.entrySet()) {
|
||||
md.append("## \uD83D\uDCC1 도메인: ").append(entry.getKey()).append("\n\n");
|
||||
|
||||
int index = 1;
|
||||
for (ToolMetadata tool : entry.getValue()) {
|
||||
md.append("### ").append(index++).append(". ").append(tool.getUid()).append("\n");
|
||||
if (tool.getDescription() != null) {
|
||||
md.append("- **설명**: ").append(tool.getDescription()).append("\n");
|
||||
}
|
||||
md.append("- **연동 방식**: `").append(tool.getIntegrationType() != null ? tool.getIntegrationType() : "DIRECT").append("`\n\n");
|
||||
|
||||
// Parameters Table
|
||||
if (tool.getParametersSchema() != null && tool.getParametersSchema().containsKey("properties")) {
|
||||
md.append("#### \u2699\uFE0F 파라미터 (Parameters)\n");
|
||||
md.append("| 파라미터명 | 타입 | 필수 여부 | 설명 |\n");
|
||||
md.append("|---|---|---|---|\n");
|
||||
|
||||
Map<String, Object> properties = (Map<String, Object>) tool.getParametersSchema().get("properties");
|
||||
List<String> required = (List<String>) tool.getParametersSchema().get("required");
|
||||
|
||||
for (Map.Entry<String, Object> prop : properties.entrySet()) {
|
||||
String name = prop.getKey();
|
||||
Map<String, Object> details = (Map<String, Object>) prop.getValue();
|
||||
String type = details.containsKey("type") ? String.valueOf(details.get("type")) : "string";
|
||||
String desc = details.containsKey("description") ? String.valueOf(details.get("description")) : "";
|
||||
String req = (required != null && required.contains(name)) ? "Y" : "N";
|
||||
|
||||
md.append("| `").append(name).append("` | `").append(type).append("` | ").append(req).append(" | ").append(desc).append(" |\n");
|
||||
}
|
||||
md.append("\n");
|
||||
}
|
||||
|
||||
// Action Prompts
|
||||
if (tool.getActionPrompts() != null && !tool.getActionPrompts().isEmpty()) {
|
||||
md.append("#### \uD83D\uDCAC 프롬프트 예시 (Action Prompts)\n");
|
||||
for (Map.Entry<String, String> prompt : tool.getActionPrompts().entrySet()) {
|
||||
md.append("- \"").append(prompt.getValue()).append("\"\n");
|
||||
}
|
||||
md.append("\n");
|
||||
}
|
||||
md.append("---\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(md.toString());
|
||||
}
|
||||
|
||||
@PostMapping("/registry/register")
|
||||
public ResponseEntity<String> registerTool(@RequestBody ToolMetadata meta) {
|
||||
redisRegistryService.saveTool(meta);
|
||||
return ResponseEntity.ok("Registered");
|
||||
}
|
||||
|
||||
@PostMapping("/registry/deregister")
|
||||
public ResponseEntity<String> deregisterTool(@RequestBody String uid) {
|
||||
redisRegistryService.removeTool(uid);
|
||||
return ResponseEntity.ok("Deregistered");
|
||||
}
|
||||
|
||||
@PostMapping("/registry/heartbeat")
|
||||
public ResponseEntity<String> heartbeat(@RequestBody String uid) {
|
||||
boolean success = redisRegistryService.refreshHeartbeat(uid);
|
||||
if (success) {
|
||||
return ResponseEntity.ok("Heartbeat updated");
|
||||
} else {
|
||||
return ResponseEntity.status(404).body("Tool not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.presentation;
|
||||
|
||||
import io.shinhanlife.dap.common.util.PodScaffolder;
|
||||
import io.shinhanlife.dap.common.util.ToolScaffolder;
|
||||
import io.shinhanlife.dap.common.util.ToolSourceUpdater;
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/scaffold")
|
||||
public class ScaffoldingController {
|
||||
|
||||
@PostMapping("/pod")
|
||||
public String scaffoldPod(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
|
||||
if (!moduleName.startsWith("axhub-tool-")) moduleName = "axhub-tool-" + moduleName;
|
||||
String port = req.getOrDefault("port", "8085");
|
||||
String shortName = moduleName.replace("axhub-tool-", "").replace("-", "");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
|
||||
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool")
|
||||
public String scaffoldTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String baseName = req.get("baseName");
|
||||
String interfaceId = req.get("interfaceId");
|
||||
String description = req.get("description");
|
||||
String group = req.getOrDefault("categoryKey", req.getOrDefault("group", "COMMON"));
|
||||
String routingType = req.getOrDefault("routingType", "HTTP");
|
||||
String moduleName = req.getOrDefault("moduleName", "axhub-tool-other");
|
||||
String author = req.get("author");
|
||||
if (author == null || author.trim().isEmpty()) author = System.getProperty("user.name");
|
||||
String date = req.get("date");
|
||||
if (date == null || date.trim().isEmpty()) date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
|
||||
return ToolScaffolder.scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, date, register);
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/tool/update")
|
||||
public String updateTool(@RequestBody Map<String, String> req) {
|
||||
try {
|
||||
String toolName = req.get("toolName");
|
||||
String domainGroup = req.getOrDefault("categoryKey", req.get("domainGroup"));
|
||||
String description = req.get("description");
|
||||
boolean register = Boolean.parseBoolean(req.getOrDefault("register", "true"));
|
||||
Boolean requiresApproval = req.containsKey("requiresApproval") ? Boolean.parseBoolean(req.get("requiresApproval")) : null;
|
||||
|
||||
ToolSourceUpdater.updateToolSource(toolName, domainGroup, description, register, requiresApproval);
|
||||
return "성공";
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/modules")
|
||||
public List<String> listModules() {
|
||||
try {
|
||||
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
if (sourceDir == null) sourceDir = System.getProperty("user.dir");
|
||||
|
||||
File dir = new File(sourceDir);
|
||||
File[] files = dir.listFiles(f -> f.isDirectory() && f.getName().startsWith("axhub-tool-") && !f.getName().equals("axhub-tool-core"));
|
||||
|
||||
if (files == null) return List.of("axhub-tool-other");
|
||||
|
||||
return Arrays.stream(files).map(File::getName).sorted().collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
return List.of("axhub-tool-other");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.redis;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@Service
|
||||
public class McpMonitorEventService {
|
||||
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* MCP Monitor browser screen subscribes to this emitter.
|
||||
*/
|
||||
public SseEmitter subscribe() {
|
||||
SseEmitter emitter = new SseEmitter(0L);
|
||||
emitters.add(emitter);
|
||||
emitter.onCompletion(() -> emitters.remove(emitter));
|
||||
emitter.onTimeout(() -> emitters.remove(emitter));
|
||||
emitter.onError(error -> emitters.remove(emitter));
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends one monitor event only when an Agent tool request changes state inside MCP.
|
||||
*/
|
||||
public void publish(String payload) {
|
||||
for (SseEmitter emitter : emitters) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("mcp-tool-trace")
|
||||
.data(payload));
|
||||
} catch (IOException | IllegalStateException error) {
|
||||
emitters.remove(emitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.redis;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContext;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class RedisToolTraceService {
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisToolTraceService.class);
|
||||
private static final String KEY_PREFIX = "mcp:request:";
|
||||
private static final String RECENT_KEY = "mcp:request:recent";
|
||||
|
||||
private final McpGatewayProperties properties;
|
||||
private final ObjectProvider<StringRedisTemplate> redisProvider;
|
||||
private final ObjectMapper json;
|
||||
private final McpMonitorEventService monitorEvents;
|
||||
private final SensitiveDataMasker masker;
|
||||
private final Map<String, AttemptState> attemptStates = new ConcurrentHashMap<>();
|
||||
|
||||
public RedisToolTraceService(McpGatewayProperties properties,
|
||||
ObjectProvider<StringRedisTemplate> redisProvider,
|
||||
ObjectMapper json,
|
||||
McpMonitorEventService monitorEvents,
|
||||
SensitiveDataMasker masker) {
|
||||
this.properties = properties;
|
||||
this.redisProvider = redisProvider;
|
||||
this.json = json;
|
||||
this.monitorEvents = monitorEvents;
|
||||
this.masker = masker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent request has entered the MCP tool gateway.
|
||||
*/
|
||||
public void started(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments) {
|
||||
save(context, metadata, arguments, "STARTED", 0, "", 0, 0, 0,
|
||||
"Agent request entered MCP", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP is attempting to call the target Tool server.
|
||||
*/
|
||||
public void attemptStarted(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||
int attempt, int maxAttempts) {
|
||||
save(context, metadata, arguments, "ATTEMPTING", 0, "", attempt, maxAttempts, 0,
|
||||
"Calling tool server", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP will retry the Tool call after backoff.
|
||||
*/
|
||||
public void retryWaiting(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||
int failedAttempt, int maxAttempts, long backoffMillis, String failureType) {
|
||||
save(context, metadata, arguments, "RETRY_WAITING", 0, failureType, failedAttempt, maxAttempts, backoffMillis,
|
||||
"Retry will be attempted after backoff", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool execution has finished.
|
||||
*/
|
||||
public void finished(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments,
|
||||
long elapsedMillis, boolean success, String failureType, String responseText) {
|
||||
save(context, metadata, arguments, success ? "SUCCESS" : "FAILED", elapsedMillis, failureType, 0, 0, 0,
|
||||
success ? "Tool call completed" : "Tool call failed", responseText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent live trace entries. One current entry is kept per request id.
|
||||
*/
|
||||
public List<String> recent() {
|
||||
if (!properties.redisTraceEnabled()) {
|
||||
return List.of("Redis trace is disabled. Set MCP_REDIS_TRACE_ENABLED=true.");
|
||||
}
|
||||
StringRedisTemplate redis = redisProvider.getIfAvailable();
|
||||
if (redis == null) {
|
||||
return List.of("RedisTemplate is not available.");
|
||||
}
|
||||
try {
|
||||
List<String> requestIds = redis.opsForList().range(RECENT_KEY, 0, 49);
|
||||
if (requestIds == null || requestIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return requestIds.stream()
|
||||
.map(requestId -> redis.opsForValue().get(KEY_PREFIX + requestId))
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.toList();
|
||||
} catch (Exception error) {
|
||||
log.warn("Redis trace read failed. message={}", error.getMessage());
|
||||
return List.of("Redis trace read failed: " + error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Historical accumulation is intentionally disabled.
|
||||
*
|
||||
* The MCP monitor is used to observe the current Agent request flow only,
|
||||
* so this method returns the same live entries as recent().
|
||||
*/
|
||||
public List<String> history() {
|
||||
return recent();
|
||||
}
|
||||
|
||||
private void save(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, String status,
|
||||
long elapsedMillis, String failureType, int attempt, int maxAttempts,
|
||||
long backoffMillis, String message, String responseText) {
|
||||
String stateKey = stateKey(context, metadata);
|
||||
if (attempt > 0 && maxAttempts > 0) {
|
||||
attemptStates.put(stateKey, new AttemptState(attempt, maxAttempts));
|
||||
}
|
||||
String payload;
|
||||
try {
|
||||
payload = tracePayload(context, metadata, arguments, status, elapsedMillis, failureType, attempt,
|
||||
maxAttempts, backoffMillis, message, responseText);
|
||||
monitorEvents.publish(payload);
|
||||
if ("SUCCESS".equals(status) || "FAILED".equals(status)) {
|
||||
attemptStates.remove(stateKey);
|
||||
}
|
||||
} catch (Exception error) {
|
||||
log.warn("MCP monitor event publish failed. requestId={} tool={} message={}",
|
||||
context.requestId(), metadata.getName(), error.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties.redisTraceEnabled()) {
|
||||
return;
|
||||
}
|
||||
StringRedisTemplate redis = redisProvider.getIfAvailable();
|
||||
if (redis == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String requestId = context.requestId();
|
||||
Duration ttl = Duration.ofSeconds(properties.redisTraceTtlSeconds());
|
||||
redis.opsForValue().set(KEY_PREFIX + requestId, payload, ttl);
|
||||
redis.opsForList().remove(RECENT_KEY, 0, requestId);
|
||||
redis.opsForList().leftPush(RECENT_KEY, requestId);
|
||||
redis.opsForList().trim(RECENT_KEY, 0, 49);
|
||||
redis.expire(RECENT_KEY, ttl);
|
||||
} catch (Exception error) {
|
||||
log.warn("Redis trace save failed. requestId={} tool={} message={}",
|
||||
context.requestId(), metadata.getName(), error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String tracePayload(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, String status,
|
||||
long elapsedMillis, String failureType, int attempt, int maxAttempts,
|
||||
long backoffMillis, String message, String responseText) throws Exception {
|
||||
AttemptState state = attemptStates.get(stateKey(context, metadata));
|
||||
int displayAttempt = attempt > 0 ? attempt : state == null ? 0 : state.attempt();
|
||||
int displayMaxAttempts = maxAttempts > 0 ? maxAttempts : state == null ? 0 : state.maxAttempts();
|
||||
int retryCount = Math.max(0, displayAttempt - 1);
|
||||
Map<String, Object> trace = new LinkedHashMap<>();
|
||||
trace.put("requestId", context.requestId());
|
||||
trace.put("traceGroupId", context.traceGroupId());
|
||||
trace.put("agentId", context.agentId());
|
||||
trace.put("userId", context.userId());
|
||||
trace.put("clientAddress", context.clientAddress());
|
||||
trace.put("toolName", metadata.getName());
|
||||
trace.put("operationType", metadata.getOperationType() != null ? metadata.getOperationType().name() : "READ");
|
||||
trace.put("status", status);
|
||||
trace.put("message", message == null ? "" : message);
|
||||
trace.put("failureType", failureType == null ? "" : failureType);
|
||||
trace.put("attempt", displayAttempt);
|
||||
trace.put("maxAttempts", displayMaxAttempts);
|
||||
trace.put("retryCount", retryCount);
|
||||
trace.put("backoffMillis", backoffMillis);
|
||||
trace.put("elapsedMillis", elapsedMillis);
|
||||
trace.put("idempotencyKeyHash", hashText(arguments.path("idempotencyKey").asText("")));
|
||||
|
||||
List<String> argNames = new ArrayList<>();
|
||||
arguments.fieldNames().forEachRemaining(argNames::add);
|
||||
trace.put("argumentNames", argNames);
|
||||
|
||||
trace.put("arguments", masker.mask(arguments).toString());
|
||||
trace.put("responseSummary", responseSummary(responseText));
|
||||
trace.put("timestamp", Instant.now().toString());
|
||||
return json.writeValueAsString(trace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis Trace에는 Tool 응답 원문을 저장하지 않고 size/hash/상태/카운트만 저장합니다.
|
||||
*/
|
||||
Map<String, Object> responseSummary(String responseText) {
|
||||
Map<String, Object> summary = new LinkedHashMap<>();
|
||||
if (responseText == null || responseText.isBlank()) {
|
||||
summary.put("bytes", 0);
|
||||
summary.put("sha256", "");
|
||||
return summary;
|
||||
}
|
||||
byte[] bytes = responseText.getBytes(StandardCharsets.UTF_8);
|
||||
summary.put("bytes", bytes.length);
|
||||
summary.put("sha256", sha256(bytes));
|
||||
try {
|
||||
JsonNode root = json.readTree(responseText);
|
||||
summary.put("success", root.path("success").asBoolean(false));
|
||||
summary.put("status", root.path("status").asText(""));
|
||||
summary.put("toolName", root.path("toolName").asText(""));
|
||||
summary.put("pageSize", root.path("pageSize").asInt(0));
|
||||
summary.put("pageCount", root.path("pageCount").asInt(0));
|
||||
summary.put("returnedCount", root.path("returnedCount").asInt(0));
|
||||
summary.put("totalCount", root.path("totalCount").asLong(0));
|
||||
summary.put("hasMore", root.path("hasMore").asBoolean(false));
|
||||
summary.put("nextCursor", root.path("nextCursor").asText(""));
|
||||
summary.put("message", root.path("message").asText(""));
|
||||
summary.put("resultRef", root.path("resultRef").asText(""));
|
||||
} catch (Exception ignored) {
|
||||
summary.put("parseable", false);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
private String sha256(byte[] bytes) {
|
||||
try {
|
||||
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes));
|
||||
} catch (Exception error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String hashText(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
return sha256(value.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String stateKey(McpRequestContext context, ToolMetadata metadata) {
|
||||
return context.requestId() + ":" + metadata.getName();
|
||||
}
|
||||
|
||||
private record AttemptState(int attempt, int maxAttempts) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.registry;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.registry
|
||||
* @className RedisRegistryService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RedisRegistryService {
|
||||
|
||||
private final RedisTemplate<String, ToolMetadata> redisTemplate;
|
||||
private static final String KEY_PREFIX = "mcp:tool:";
|
||||
private static final Duration DEFAULT_TTL = Duration.ofSeconds(45); // 실무 하트비트 주기 반영
|
||||
|
||||
/**
|
||||
* 툴 등록 및 갱신 (TTL 기반으로 60초 뒤 자동 만료)
|
||||
*/
|
||||
public void saveTool(ToolMetadata meta) {
|
||||
String key = KEY_PREFIX + meta.getUid();
|
||||
meta.setLastHeartbeat(System.currentTimeMillis());
|
||||
redisTemplate.opsForValue().set(key, meta, DEFAULT_TTL);
|
||||
log.info(" [RedisRegistry] 툴 등록 완료: {}", meta.getUid());
|
||||
}
|
||||
|
||||
/**
|
||||
* 하트비트 갱신 (TTL 초기화)
|
||||
*/
|
||||
public boolean refreshHeartbeat(String uid) {
|
||||
String key = KEY_PREFIX + uid;
|
||||
Boolean exists = redisTemplate.expire(key, DEFAULT_TTL);
|
||||
if (Boolean.TRUE.equals(exists)) {
|
||||
log.debug(" [RedisRegistry] 하트비트 갱신: {}", uid);
|
||||
return true;
|
||||
} else {
|
||||
log.warn(" [RedisRegistry] 존재하지 않는 툴에 대한 하트비트 요청: {}", uid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public List<String> getAvailablePods(String uid) {
|
||||
ToolMetadata tool = getTool(uid);
|
||||
|
||||
if (tool != null && tool.getPodUrl() != null) {
|
||||
return List.of(tool.getPodUrl());
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
/**
|
||||
* 실행 시 툴 정보 조회 (Tool Execution 시 참조)
|
||||
*/
|
||||
public ToolMetadata getTool(String uid) {
|
||||
return redisTemplate.opsForValue().get(KEY_PREFIX + uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 툴 이름으로 정보 조회
|
||||
*/
|
||||
public ToolMetadata getToolByName(String name) {
|
||||
List<ToolMetadata> allTools = getAllTools();
|
||||
for (ToolMetadata tool : allTools) {
|
||||
if (name.equals(tool.getName())) {
|
||||
return tool;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록된 모든 활성 툴 목록 조회
|
||||
*/
|
||||
public List<ToolMetadata> getAllTools() {
|
||||
Set<String> keys = redisTemplate.keys(KEY_PREFIX + "*");
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<ToolMetadata> tools = redisTemplate.opsForValue().multiGet(keys);
|
||||
return tools != null ? tools.stream().filter(Objects::nonNull).collect(Collectors.toList()) : List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 툴 명시적 제거 (Deregister)
|
||||
*/
|
||||
public void removeTool(String uid) {
|
||||
redisTemplate.delete(KEY_PREFIX + uid);
|
||||
log.info(" [RedisRegistry] 툴 삭제 완료: {}", uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Tool별 장애 확산을 막기 위한 Circuit Breaker입니다.
|
||||
*
|
||||
* 현재 OPEN 조건은 사용자가 요청한 기준에 맞춰
|
||||
* TIMEOUT 3회 이상 또는 SERVER_ERROR(5xx) 3회 이상입니다.
|
||||
*/
|
||||
public class CircuitBreaker {
|
||||
private static final int TIMEOUT_OPEN_THRESHOLD = 3;
|
||||
private static final int SERVER_ERROR_OPEN_THRESHOLD = 3;
|
||||
|
||||
private final String name;
|
||||
private final long openDurationMillis;
|
||||
private final Clock clock;
|
||||
private CircuitBreakerState state = CircuitBreakerState.CLOSED;
|
||||
private int timeoutFailures;
|
||||
private int serverErrorFailures;
|
||||
private FailureType lastFailureType;
|
||||
private String openedReason = "";
|
||||
private Instant openedAt;
|
||||
|
||||
public CircuitBreaker(String name, int failureThreshold, long openDurationMillis, Clock clock) {
|
||||
this.name = name;
|
||||
this.openDurationMillis = Math.max(1, openDurationMillis);
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 Tool 호출 전에 현재 회로가 호출 가능한 상태인지 확인합니다.
|
||||
*/
|
||||
public synchronized void beforeCall() {
|
||||
if (state != CircuitBreakerState.OPEN) {
|
||||
return;
|
||||
}
|
||||
long openedMillis = openedAt == null ? 0 : clock.millis() - openedAt.toEpochMilli();
|
||||
if (openedMillis >= openDurationMillis) {
|
||||
state = CircuitBreakerState.HALF_OPEN;
|
||||
return;
|
||||
}
|
||||
throw new ToolExecutionException(FailureType.CIRCUIT_OPEN, "Circuit breaker is open: " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 호출 성공 시 실패 카운터를 초기화하고 CLOSED 상태로 복구합니다.
|
||||
*/
|
||||
public synchronized void recordSuccess() {
|
||||
timeoutFailures = 0;
|
||||
serverErrorFailures = 0;
|
||||
lastFailureType = null;
|
||||
openedReason = "";
|
||||
openedAt = null;
|
||||
state = CircuitBreakerState.CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 장애 유형별 실패 횟수를 집계하고 조건에 맞으면 OPEN 상태로 전환합니다.
|
||||
*/
|
||||
public synchronized void recordFailure(FailureType failureType) {
|
||||
if (failureType == FailureType.TIMEOUT) {
|
||||
timeoutFailures++;
|
||||
lastFailureType = failureType;
|
||||
} else if (failureType == FailureType.SERVER_ERROR) {
|
||||
serverErrorFailures++;
|
||||
lastFailureType = failureType;
|
||||
} else if (state == CircuitBreakerState.HALF_OPEN && isCircuitBreakerFailure(failureType)) {
|
||||
lastFailureType = failureType;
|
||||
open("HALF_OPEN_TEST_FAILED");
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == CircuitBreakerState.HALF_OPEN) {
|
||||
open(failureType.name() + "_IN_HALF_OPEN");
|
||||
return;
|
||||
}
|
||||
if (timeoutFailures >= TIMEOUT_OPEN_THRESHOLD) {
|
||||
open("TIMEOUT_3_OR_MORE");
|
||||
}
|
||||
if (serverErrorFailures >= SERVER_ERROR_OPEN_THRESHOLD) {
|
||||
open("SERVER_ERROR_5XX_3_OR_MORE");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized CircuitBreakerState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP Monitor 화면에 표시할 현재 상태 스냅샷입니다.
|
||||
*/
|
||||
public synchronized CircuitBreakerSnapshot snapshot() {
|
||||
long remainingOpenMillis = 0;
|
||||
if (state == CircuitBreakerState.OPEN && openedAt != null) {
|
||||
long elapsed = Math.max(0, clock.millis() - openedAt.toEpochMilli());
|
||||
remainingOpenMillis = Math.max(0, openDurationMillis - elapsed);
|
||||
}
|
||||
return new CircuitBreakerSnapshot(
|
||||
name,
|
||||
state.name(),
|
||||
timeoutFailures,
|
||||
serverErrorFailures,
|
||||
TIMEOUT_OPEN_THRESHOLD,
|
||||
SERVER_ERROR_OPEN_THRESHOLD,
|
||||
lastFailureType == null ? "" : lastFailureType.name(),
|
||||
openedReason,
|
||||
openedAt == null ? "" : openedAt.toString(),
|
||||
remainingOpenMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트 후 화면에서 회로를 수동 초기화할 때 사용합니다.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
recordSuccess();
|
||||
}
|
||||
|
||||
private void open(String reason) {
|
||||
state = CircuitBreakerState.OPEN;
|
||||
openedAt = Instant.now(clock);
|
||||
openedReason = reason;
|
||||
}
|
||||
|
||||
private boolean isCircuitBreakerFailure(FailureType failureType) {
|
||||
return failureType == FailureType.TIMEOUT
|
||||
|| failureType == FailureType.NETWORK_ERROR
|
||||
|| failureType == FailureType.SERVER_ERROR
|
||||
|| failureType == FailureType.INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP Monitor 화면에서 Circuit Breaker 상태를 확인하고 초기화하기 위한 API입니다.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/internal/circuit-breakers")
|
||||
public class CircuitBreakerMonitorController {
|
||||
private final CircuitBreakerService circuitBreakers;
|
||||
|
||||
public CircuitBreakerMonitorController(CircuitBreakerService circuitBreakers) {
|
||||
this.circuitBreakers = circuitBreakers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool별 Circuit Breaker 상태 목록을 반환합니다.
|
||||
*/
|
||||
@GetMapping
|
||||
public List<CircuitBreakerSnapshot> snapshots() {
|
||||
return circuitBreakers.snapshots();
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 Circuit Breaker를 CLOSED 상태로 초기화합니다.
|
||||
*/
|
||||
@PostMapping("/reset")
|
||||
public Map<String, Object> reset() {
|
||||
circuitBreakers.resetAll();
|
||||
return Map.of("reset", true, "items", circuitBreakers.snapshots());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
/**
|
||||
* Tool 이름별 Circuit Breaker 인스턴스를 관리하는 서비스입니다.
|
||||
*
|
||||
* registry에 등록된 Tool별로 장애 상태를 독립적으로 관리합니다.
|
||||
*/
|
||||
public class CircuitBreakerService {
|
||||
private final McpGatewayProperties properties;
|
||||
private final Map<String, CircuitBreaker> breakers = new ConcurrentHashMap<>();
|
||||
|
||||
public CircuitBreakerService(McpGatewayProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이름에 해당하는 Circuit Breaker를 반환하고, 없으면 새로 생성합니다.
|
||||
*/
|
||||
public CircuitBreaker breaker(String name) {
|
||||
return breaker(name, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool별 정책이 있으면 해당 threshold/open 시간을 우선 사용해 Circuit Breaker를 생성합니다.
|
||||
*/
|
||||
public CircuitBreaker breaker(String name, int failureThreshold, long openMillis) {
|
||||
int effectiveThreshold = failureThreshold > 0 ? failureThreshold : properties.circuitBreakerFailureThreshold();
|
||||
long effectiveOpenMillis = openMillis > 0 ? openMillis : properties.circuitBreakerOpenMillis();
|
||||
return breakers.computeIfAbsent(name, key -> new CircuitBreaker(
|
||||
key,
|
||||
effectiveThreshold,
|
||||
effectiveOpenMillis,
|
||||
Clock.systemUTC()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool registry가 갱신될 때 Circuit Breaker 정책도 새 값으로 갱신합니다.
|
||||
*/
|
||||
public void refresh(String name, int failureThreshold, long openMillis) {
|
||||
int effectiveThreshold = failureThreshold > 0 ? failureThreshold : properties.circuitBreakerFailureThreshold();
|
||||
long effectiveOpenMillis = openMillis > 0 ? openMillis : properties.circuitBreakerOpenMillis();
|
||||
breakers.put(name, new CircuitBreaker(name, effectiveThreshold, effectiveOpenMillis, Clock.systemUTC()));
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP Monitor 화면에 표시할 전체 Circuit Breaker 상태를 반환합니다.
|
||||
*/
|
||||
public List<CircuitBreakerSnapshot> snapshots() {
|
||||
return breakers.values().stream()
|
||||
.map(CircuitBreaker::snapshot)
|
||||
.sorted(Comparator.comparing(CircuitBreakerSnapshot::name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트 후 모든 Circuit Breaker 상태를 CLOSED로 초기화합니다.
|
||||
*/
|
||||
public void resetAll() {
|
||||
breakers.values().forEach(CircuitBreaker::reset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
/**
|
||||
* MCP Monitor 화면에 내려주는 Circuit Breaker 상태 정보입니다.
|
||||
*/
|
||||
public record CircuitBreakerSnapshot(
|
||||
String name,
|
||||
String state,
|
||||
int timeoutFailures,
|
||||
int serverErrorFailures,
|
||||
int timeoutOpenThreshold,
|
||||
int serverErrorOpenThreshold,
|
||||
String lastFailureType,
|
||||
String openedReason,
|
||||
String openedAt,
|
||||
long remainingOpenMillis
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
/**
|
||||
* Circuit Breaker의 현재 상태입니다.
|
||||
*
|
||||
* CLOSED는 정상 호출 가능, OPEN은 호출 차단, HALF_OPEN은 복구 확인 상태를 의미합니다.
|
||||
*/
|
||||
public enum CircuitBreakerState {
|
||||
CLOSED,
|
||||
OPEN,
|
||||
HALF_OPEN
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
/**
|
||||
* Tool/EIMS/Tool 서버 호출 실패를 업무적으로 구분하기 위한 장애 유형입니다.
|
||||
*
|
||||
* Retry, Circuit Breaker, 감사 로그에서 같은 기준으로 오류를 판단합니다.
|
||||
*/
|
||||
public enum FailureType {
|
||||
TIMEOUT,
|
||||
CLIENT_ERROR,
|
||||
SERVER_ERROR,
|
||||
NETWORK_ERROR,
|
||||
AUTHORIZATION_ERROR,
|
||||
BUSINESS_ERROR,
|
||||
HALLUCINATION_GUARDRAIL,
|
||||
TOOL_DELETED,
|
||||
CIRCUIT_OPEN,
|
||||
TOO_LARGE_RESULT,
|
||||
INTERNAL_ERROR
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
/**
|
||||
* Tool 호출 재시도 정책입니다.
|
||||
*
|
||||
* 최대 재시도 횟수, 최초 대기 시간, Backoff 배율, 최대 대기 시간을 담습니다.
|
||||
*/
|
||||
public record RetryPolicy(int maxAttempts, long initialBackoffMillis, double backoffMultiplier, long maxBackoffMillis) {
|
||||
/**
|
||||
* WRITE 요청처럼 재시도하면 위험한 경우 사용하는 비활성 정책입니다.
|
||||
*/
|
||||
public static RetryPolicy disabled() {
|
||||
return new RetryPolicy(1, 0, 1.0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 실패한 시도 횟수에 따라 다음 재시도 전 대기 시간을 계산합니다.
|
||||
*/
|
||||
public long backoffMillis(int failedAttempt) {
|
||||
if (failedAttempt < 1 || initialBackoffMillis < 1) {
|
||||
return 0;
|
||||
}
|
||||
double backoff = initialBackoffMillis * Math.pow(backoffMultiplier, failedAttempt - 1);
|
||||
if (maxBackoffMillis > 0) {
|
||||
backoff = Math.min(backoff, maxBackoffMillis);
|
||||
}
|
||||
return Math.max(0, Math.round(backoff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.resilience;
|
||||
|
||||
/**
|
||||
* Tool 실행 중 발생한 오류를 FailureType과 함께 전달하는 공통 예외입니다.
|
||||
*
|
||||
* 상위 GatewayToolExecutor가 이 예외를 기준으로 감사 로그, Redis Trace, 재시도 여부를 판단합니다.
|
||||
*/
|
||||
public class ToolExecutionException extends RuntimeException {
|
||||
private final FailureType failureType;
|
||||
|
||||
public ToolExecutionException(String message) {
|
||||
this(FailureType.BUSINESS_ERROR, message);
|
||||
}
|
||||
|
||||
public ToolExecutionException(FailureType failureType, String message) {
|
||||
super(message);
|
||||
this.failureType = failureType;
|
||||
}
|
||||
|
||||
public ToolExecutionException(FailureType failureType, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.failureType = failureType;
|
||||
}
|
||||
|
||||
public FailureType failureType() {
|
||||
return failureType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.security;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Agent가 MCP 서버로 보낸 헤더 정보를 표준화한 요청 컨텍스트입니다.
|
||||
*
|
||||
* 이후 권한 검증, 감사 로그, Redis Trace, Tool 서버 호출 시 공통으로 사용됩니다.
|
||||
*/
|
||||
public record McpRequestContext(
|
||||
String requestId,
|
||||
String traceGroupId,
|
||||
String clientAddress,
|
||||
String agentId,
|
||||
String userId,
|
||||
Set<String> roles,
|
||||
Set<String> allowedTools,
|
||||
boolean claimsTrusted,
|
||||
boolean writeApproved
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.security;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
/**
|
||||
* 현재 HTTP 요청에서 Agent/User/권한 관련 헤더를 읽어 McpRequestContext로 변환합니다.
|
||||
*
|
||||
* MCP Tool 메소드 자체는 HTTP 객체를 직접 받지 않으므로, 이 클래스가 요청 정보를 꺼내주는 역할을 합니다.
|
||||
*/
|
||||
public class McpRequestContextResolver {
|
||||
/**
|
||||
* 현재 요청의 헤더를 읽어 권한/추적용 컨텍스트를 생성합니다.
|
||||
*/
|
||||
public McpRequestContext current() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
return new McpRequestContext(requestId, requestId, "internal", "internal", "internal", Set.of(), Set.of(), true, true);
|
||||
}
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
String requestId = firstNonBlank(request.getHeader("X-Request-Id"), UUID.randomUUID().toString());
|
||||
return new McpRequestContext(
|
||||
requestId,
|
||||
firstNonBlank(request.getHeader("X-MCP-Trace-Group-Id"), requestId),
|
||||
request.getRemoteAddr(),
|
||||
firstNonBlank(request.getHeader("X-MCP-Agent-Id"), "anonymous"),
|
||||
firstNonBlank(request.getHeader("X-MCP-User-Id"), "anonymous"),
|
||||
csvHeader(request.getHeader("X-MCP-Roles")),
|
||||
csvHeader(request.getHeader("X-MCP-Allowed-Tools")),
|
||||
Boolean.parseBoolean(firstNonBlank(request.getHeader("X-MCP-Claims-Trusted"), "false")),
|
||||
Boolean.parseBoolean(firstNonBlank(request.getHeader("X-MCP-Write-Approved"), "false")));
|
||||
}
|
||||
|
||||
private String firstNonBlank(String value, String fallback) {
|
||||
return value == null || value.isBlank() ? fallback : value.trim();
|
||||
}
|
||||
|
||||
private Set<String> csvHeader(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return Set.of();
|
||||
}
|
||||
return Arrays.stream(value.split(","))
|
||||
.map(String::trim)
|
||||
.filter(item -> !item.isBlank())
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.security;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.OperationType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
/**
|
||||
* Agent가 특정 Tool을 호출할 수 있는지 판단하는 권한 검증 서비스입니다.
|
||||
*
|
||||
* 서버 전체 허용 Tool, Agent가 보낸 allowed-tools 헤더, WRITE 승인 여부를 함께 확인합니다.
|
||||
*/
|
||||
public class ToolAuthorizationService {
|
||||
private final McpGatewayProperties properties;
|
||||
|
||||
public ToolAuthorizationService(McpGatewayProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool 실행 직전에 호출 가능 여부를 검사하고, 거부 시 ToolExecutionException을 발생시킵니다.
|
||||
*/
|
||||
public void authorize(McpRequestContext context, ToolMetadata metadata) {
|
||||
if (properties.agentClaimsRequired() && context.allowedTools().isEmpty()) {
|
||||
throw new ToolExecutionException(FailureType.AUTHORIZATION_ERROR, "Missing agent authorization claims");
|
||||
}
|
||||
if (properties.trustedClaimsRequired() && !context.claimsTrusted()) {
|
||||
throw new ToolExecutionException(FailureType.AUTHORIZATION_ERROR, "Agent authorization claims are not trusted");
|
||||
}
|
||||
Set<String> serverAllowedTools = properties.allowedToolSet();
|
||||
if (!isAllowed(serverAllowedTools, metadata.getName())) {
|
||||
throw new ToolExecutionException(FailureType.AUTHORIZATION_ERROR, "Tool is not allowed by server policy: " + metadata.getName());
|
||||
}
|
||||
if (!isAllowed(context.allowedTools(), metadata.getName())) {
|
||||
throw new ToolExecutionException(FailureType.AUTHORIZATION_ERROR, "Tool is not allowed by agent claims: " + metadata.getName());
|
||||
}
|
||||
if (properties.writeApprovalRequired()
|
||||
&& (metadata.getOperationType() == OperationType.WRITE || (metadata.getRequiresApproval() != null && metadata.getRequiresApproval()))
|
||||
&& !context.writeApproved()) {
|
||||
throw new ToolExecutionException(FailureType.AUTHORIZATION_ERROR, "Write tool requires approval: " + metadata.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 빈 목록은 제한 없음, "*"는 모든 Tool 허용을 의미합니다.
|
||||
*/
|
||||
private boolean isAllowed(Set<String> allowedTools, String toolName) {
|
||||
return allowedTools.isEmpty() || allowedTools.contains("*") || allowedTools.contains(toolName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.service;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.RetryPolicy;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.OperationType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.GuardrailService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContext;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.security.McpRequestContextResolver;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.audit.AuditLogService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.CircuitBreaker;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.CircuitBreakerService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.security.ToolAuthorizationService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.redis.RedisToolTraceService;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.tool.large.LargeToolResponseService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.tool.large.PaginationRequestValidator;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.tool.result.ToolExecutionResultFormatter;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.tool.result.ToolExecutionResult;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.ToolResponseGuardrailService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.transport.ToolInvoker;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ExecuteService {
|
||||
|
||||
private final ToolPlanner planner;
|
||||
private final KillSwitchService killSwitchService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final SensitiveDataMasker dataMasker;
|
||||
private final GuardrailService guardrailService;
|
||||
private final McpRequestContextResolver contextResolver;
|
||||
private final AuditLogService auditLogService;
|
||||
private final CircuitBreakerService circuitBreakerService;
|
||||
private final ToolAuthorizationService authorizationService;
|
||||
private final RedisToolTraceService redisTrace;
|
||||
private final McpGatewayProperties properties;
|
||||
private final LargeToolResponseService largeResponses;
|
||||
private final PaginationRequestValidator paginationValidator;
|
||||
private final ToolExecutionResultFormatter resultFormatter;
|
||||
private final ToolResponseGuardrailService responseGuardrail;
|
||||
private final ToolInvoker toolInvoker;
|
||||
private final ExecutorService executor;
|
||||
|
||||
public ExecuteService(ToolPlanner planner,
|
||||
KillSwitchService killSwitchService,
|
||||
ObjectMapper objectMapper,
|
||||
SensitiveDataMasker dataMasker,
|
||||
GuardrailService guardrailService,
|
||||
McpRequestContextResolver contextResolver,
|
||||
AuditLogService auditLogService,
|
||||
CircuitBreakerService circuitBreakerService,
|
||||
ToolAuthorizationService authorizationService,
|
||||
RedisToolTraceService redisTrace,
|
||||
McpGatewayProperties properties,
|
||||
LargeToolResponseService largeResponses,
|
||||
PaginationRequestValidator paginationValidator,
|
||||
ToolExecutionResultFormatter resultFormatter,
|
||||
ToolResponseGuardrailService responseGuardrail,
|
||||
ToolInvoker toolInvoker) {
|
||||
this.planner = planner;
|
||||
this.killSwitchService = killSwitchService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.dataMasker = dataMasker;
|
||||
this.guardrailService = guardrailService;
|
||||
this.contextResolver = contextResolver;
|
||||
this.auditLogService = auditLogService;
|
||||
this.circuitBreakerService = circuitBreakerService;
|
||||
this.authorizationService = authorizationService;
|
||||
this.redisTrace = redisTrace;
|
||||
this.properties = properties;
|
||||
this.largeResponses = largeResponses;
|
||||
this.paginationValidator = paginationValidator;
|
||||
this.resultFormatter = resultFormatter;
|
||||
this.responseGuardrail = responseGuardrail;
|
||||
this.toolInvoker = toolInvoker;
|
||||
|
||||
this.executor = new ThreadPoolExecutor(
|
||||
properties.toolExecutorCorePoolSize(),
|
||||
properties.toolExecutorMaxPoolSize(),
|
||||
properties.toolExecutorKeepAliveSeconds(),
|
||||
TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(properties.toolExecutorQueueCapacity()),
|
||||
new ThreadPoolExecutor.AbortPolicy()
|
||||
);
|
||||
}
|
||||
|
||||
public Object execute(Map<String, Object> payload, String tenantId) {
|
||||
log.info(" [ExecuteService] 전체 실행 흐름 제어 시작");
|
||||
|
||||
killSwitchService.checkAgent(tenantId);
|
||||
|
||||
Map<String, Object> params = payload.containsKey("params") ? (Map<String, Object>) payload.get("params") : null;
|
||||
String toolName = params != null ? (String) params.get("name") : (String) payload.get("toolName");
|
||||
killSwitchService.checkTool(toolName);
|
||||
|
||||
var planObj = planner.createPlan(payload, tenantId);
|
||||
ToolMetadata metadata = (ToolMetadata) planObj;
|
||||
|
||||
McpRequestContext context = contextResolver.current();
|
||||
|
||||
ObjectNode argumentsNode = objectMapper.createObjectNode();
|
||||
if (params != null && params.containsKey("arguments")) {
|
||||
argumentsNode = objectMapper.valueToTree(params.get("arguments"));
|
||||
}
|
||||
|
||||
long startedAt = System.nanoTime();
|
||||
auditLogService.toolStarted(context, toolName, argumentsNode);
|
||||
redisTrace.started(context, metadata, argumentsNode);
|
||||
|
||||
try {
|
||||
authorizationService.authorize(context, metadata);
|
||||
if (params != null && params.containsKey("arguments")) {
|
||||
guardrailService.validate(metadata, argumentsNode);
|
||||
}
|
||||
|
||||
if (metadata.getIntegrationType() != null) {
|
||||
killSwitchService.checkRoute(metadata.getIntegrationType());
|
||||
}
|
||||
|
||||
Object result = executeWithResilience(context, metadata, argumentsNode, payload);
|
||||
|
||||
if (result instanceof com.fasterxml.jackson.databind.JsonNode) {
|
||||
// Apply Output Guardrail
|
||||
String wrappedResponse = responseGuardrail.validateAndWrap(metadata, context.requestId(), (com.fasterxml.jackson.databind.JsonNode) result);
|
||||
|
||||
// Format the result (Restore legacy raw format as Map/List for backward compatibility)
|
||||
result = objectMapper.readValue(wrappedResponse, Object.class);
|
||||
}
|
||||
|
||||
long elapsedMillis = elapsedMillis(startedAt);
|
||||
|
||||
String responseText = "";
|
||||
try { responseText = objectMapper.writeValueAsString(result); } catch (Exception ignore) {}
|
||||
|
||||
auditLogService.toolFinished(context, metadata.getName(), elapsedMillis, true, "");
|
||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, true, "", responseText);
|
||||
|
||||
return result;
|
||||
} catch (ToolExecutionException error) {
|
||||
long elapsedMillis = elapsedMillis(startedAt);
|
||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, error.failureType().name());
|
||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, error.failureType().name(), "");
|
||||
throw error;
|
||||
} catch (Exception error) {
|
||||
long elapsedMillis = elapsedMillis(startedAt);
|
||||
auditLogService.toolFinished(context, toolName, elapsedMillis, false, FailureType.INTERNAL_ERROR.name());
|
||||
redisTrace.finished(context, metadata, argumentsNode, elapsedMillis, false, FailureType.INTERNAL_ERROR.name(), "");
|
||||
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool execution failed: " + toolName, error);
|
||||
}
|
||||
}
|
||||
|
||||
private Object executeWithResilience(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, Map<String, Object> payload) {
|
||||
RetryPolicy retryPolicy = retryPolicy(metadata, arguments);
|
||||
|
||||
int failureThreshold = (metadata.getCircuitBreakerFailureThreshold() != null && metadata.getCircuitBreakerFailureThreshold() > 0)
|
||||
? metadata.getCircuitBreakerFailureThreshold()
|
||||
: properties.circuitBreakerFailureThreshold();
|
||||
|
||||
long openMillis = (metadata.getCircuitBreakerOpenMillis() != null && metadata.getCircuitBreakerOpenMillis() > 0)
|
||||
? metadata.getCircuitBreakerOpenMillis()
|
||||
: properties.circuitBreakerOpenMillis();
|
||||
|
||||
CircuitBreaker circuitBreaker = circuitBreakerService.breaker("tool:" + metadata.getName(),
|
||||
failureThreshold, openMillis);
|
||||
|
||||
ToolExecutionException lastError = null;
|
||||
for (int attempt = 1; attempt <= retryPolicy.maxAttempts(); attempt++) {
|
||||
try {
|
||||
redisTrace.attemptStarted(context, metadata, arguments, attempt, retryPolicy.maxAttempts());
|
||||
circuitBreaker.beforeCall();
|
||||
Object result = executeOnce(context, metadata, arguments, payload);
|
||||
circuitBreaker.recordSuccess();
|
||||
return result;
|
||||
} catch (ToolExecutionException error) {
|
||||
lastError = error;
|
||||
circuitBreaker.recordFailure(error.failureType());
|
||||
if (!shouldRetry(metadata, arguments, error.failureType(), attempt, retryPolicy)) {
|
||||
throw error;
|
||||
}
|
||||
long backoffMillis = retryPolicy.backoffMillis(attempt);
|
||||
redisTrace.retryWaiting(context, metadata, arguments, attempt, retryPolicy.maxAttempts(), backoffMillis,
|
||||
error.failureType().name());
|
||||
sleepBeforeRetry(metadata.getName(), attempt, backoffMillis, error.failureType());
|
||||
}
|
||||
}
|
||||
throw lastError == null
|
||||
? new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool execution failed: " + metadata.getName())
|
||||
: lastError;
|
||||
}
|
||||
|
||||
private Object executeOnce(McpRequestContext context, ToolMetadata metadata, ObjectNode arguments, Map<String, Object> payload) {
|
||||
CompletableFuture<Object> future;
|
||||
|
||||
try {
|
||||
future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
String targetUrl = "http://localhost:8084"; // Fallback
|
||||
if (metadata.getPodUrl() != null && !metadata.getPodUrl().isEmpty()) {
|
||||
targetUrl = metadata.getPodUrl();
|
||||
}
|
||||
String executeApiUrl = targetUrl + "/mcp/api/v1/tools/call";
|
||||
|
||||
ObjectNode pageArguments = paginationValidator.normalize(arguments);
|
||||
LargeToolResponseService.Collector collector = largeResponses.newCollector(metadata.getName(), context.requestId());
|
||||
|
||||
while (true) {
|
||||
Map<String, Object> pagePayload = new java.util.HashMap<>(payload);
|
||||
if (pagePayload.containsKey("params")) {
|
||||
Map<String, Object> params = new java.util.HashMap<>((Map<String, Object>) pagePayload.get("params"));
|
||||
params.put("arguments", objectMapper.convertValue(pageArguments, Map.class));
|
||||
pagePayload.put("params", params);
|
||||
} else {
|
||||
pagePayload.put("arguments", objectMapper.convertValue(pageArguments, Map.class));
|
||||
}
|
||||
|
||||
try {
|
||||
log.info(" [ExecuteService] 요청 페이로드(마스킹 적용): {}", objectMapper.writeValueAsString(dataMasker.mask(objectMapper.valueToTree(pagePayload))));
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
JsonNode data = null;
|
||||
try {
|
||||
data = toolInvoker.invoke(pagePayload, executeApiUrl);
|
||||
} catch (Exception e) {
|
||||
if (executeApiUrl.contains("http://tool-")) {
|
||||
String fallbackUrl = executeApiUrl.replaceAll("http://tool-[a-zA-Z0-9-]+", "http://localhost");
|
||||
log.warn(" [ExecuteService] 호스트를 찾을 수 없어 localhost로 재시도합니다: {}", fallbackUrl);
|
||||
try {
|
||||
data = toolInvoker.invoke(pagePayload, fallbackUrl);
|
||||
} catch (Exception ex) {
|
||||
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패 (localhost 재시도 포함): " + ex.getMessage());
|
||||
}
|
||||
} else {
|
||||
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod 호출 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
collector.accept(data);
|
||||
if (!collector.shouldFetchNextPage()) {
|
||||
return collector.finish();
|
||||
}
|
||||
pageArguments.put("cursor", collector.nextCursor());
|
||||
pageArguments.put("pageSize", collector.pageSize());
|
||||
}
|
||||
} catch (ToolExecutionException error) {
|
||||
throw error;
|
||||
} catch (Exception error) {
|
||||
throw new ToolExecutionException(
|
||||
FailureType.INTERNAL_ERROR,
|
||||
"Tool execution failed: " + metadata.getName(),
|
||||
error
|
||||
);
|
||||
}
|
||||
}, executor);
|
||||
} catch (RuntimeException error) {
|
||||
throw new ToolExecutionException(
|
||||
FailureType.SERVER_ERROR,
|
||||
"MCP Tool 실행 큐가 가득 찼습니다: " + metadata.getName(),
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return future.get(timeoutMillis(metadata), TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException error) {
|
||||
future.cancel(true);
|
||||
throw new ToolExecutionException(FailureType.TIMEOUT, "Tool execution timed out: " + metadata.getName(), error);
|
||||
} catch (ExecutionException error) {
|
||||
if (error.getCause() instanceof ToolExecutionException toolError) {
|
||||
throw toolError;
|
||||
}
|
||||
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool execution failed: " + metadata.getName(), error);
|
||||
} catch (InterruptedException error) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool 실행이 중단되었습니다: " + metadata.getName(), error);
|
||||
}
|
||||
}
|
||||
|
||||
private RetryPolicy retryPolicy(ToolMetadata metadata, ObjectNode arguments) {
|
||||
if (!retryAllowedByOperation(metadata, arguments)) {
|
||||
return RetryPolicy.disabled();
|
||||
}
|
||||
return new RetryPolicy(properties.retryMaxAttempts(), properties.retryInitialBackoffMillis(),
|
||||
properties.retryBackoffMultiplier(), properties.retryMaxBackoffMillis());
|
||||
}
|
||||
|
||||
private boolean retryAllowedByOperation(ToolMetadata metadata, ObjectNode arguments) {
|
||||
if (metadata.getOperationType() == OperationType.READ) {
|
||||
return metadata.getRetryEnabled() != null ? metadata.getRetryEnabled() : true;
|
||||
}
|
||||
return arguments.hasNonNull("idempotencyKey") && !arguments.path("idempotencyKey").asText("").isBlank();
|
||||
}
|
||||
|
||||
private boolean shouldRetry(ToolMetadata metadata, ObjectNode arguments, FailureType failureType, int attempt, RetryPolicy retryPolicy) {
|
||||
if (attempt >= retryPolicy.maxAttempts() || !retryAllowedByOperation(metadata, arguments)) {
|
||||
return false;
|
||||
}
|
||||
return failureType == FailureType.TIMEOUT || failureType == FailureType.NETWORK_ERROR || failureType == FailureType.SERVER_ERROR;
|
||||
}
|
||||
|
||||
private void sleepBeforeRetry(String toolName, int attempt, long backoffMillis, FailureType failureType) {
|
||||
if (backoffMillis <= 0) {
|
||||
return;
|
||||
}
|
||||
log.warn("Retrying tool call. tool={} failedAttempt={} failureType={} backoffMillis={}", toolName, attempt, failureType, backoffMillis);
|
||||
try {
|
||||
Thread.sleep(backoffMillis);
|
||||
} catch (InterruptedException error) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ToolExecutionException(FailureType.INTERNAL_ERROR, "Tool 재시도가 중단되었습니다: " + toolName, error);
|
||||
}
|
||||
}
|
||||
|
||||
private long timeoutMillis(ToolMetadata metadata) {
|
||||
return (metadata.getTimeoutMillis() != null && metadata.getTimeoutMillis() > 0) ? metadata.getTimeoutMillis() : properties.toolTimeoutMillis();
|
||||
}
|
||||
|
||||
private long elapsedMillis(long startedAt) {
|
||||
return (System.nanoTime() - startedAt) / 1_000_000;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.service
|
||||
* @className KillSwitchService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KillSwitchService {
|
||||
|
||||
// 기본 제공되는 StringRedisTemplate 사용
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public void checkAgent(String tenantId) {
|
||||
String key = "mcp:kill:agent:" + tenantId;
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 에이전트 접근 시도: {}", tenantId);
|
||||
throw new SecurityException(" 비상 차단: 해당 테넌트(" + tenantId + ")의 접근이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkTool(String toolName) {
|
||||
if (toolName == null || toolName.isBlank()) return;
|
||||
|
||||
String key = "mcp:kill:tool:" + toolName;
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 툴 실행 시도: {}", toolName);
|
||||
throw new SecurityException(" 비상 차단: 해당 툴(" + toolName + ")의 실행이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkRoute(String integrationType) {
|
||||
if (integrationType == null || integrationType.isBlank()) return;
|
||||
|
||||
String key = "mcp:kill:route:" + integrationType;
|
||||
if ("true".equalsIgnoreCase(stringRedisTemplate.opsForValue().get(key))) {
|
||||
log.warn(" [KillSwitch] 차단된 라우트 통신 시도: {}", integrationType);
|
||||
throw new SecurityException(" 비상 차단: 해당 레거시 라우트(" + integrationType + ")로의 통신이 관리자에 의해 차단되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
// 관리자 API용 토글 메서드
|
||||
public void toggleKillSwitch(String type, String target, boolean state) {
|
||||
String key = "mcp:kill:" + type + ":" + target;
|
||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(state));
|
||||
log.info(" [KillSwitch] 상태 변경: {} -> {} (차단 상태: {})", type, target, state);
|
||||
}
|
||||
|
||||
// 관리자 API용 삭제 메서드 (Redis 키 자체를 완전 삭제)
|
||||
public void removeKillSwitch(String type, String target) {
|
||||
String key = "mcp:kill:" + type + ":" + target;
|
||||
stringRedisTemplate.delete(key);
|
||||
log.info(" [KillSwitch] 차단 키 완전 삭제: {} -> {}", type, target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.service;
|
||||
|
||||
import io.shinhanlife.dap.common.mcp.security.SecurityProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.GatewayFallbackProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.service
|
||||
* @className ToolPlanner
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ToolPlanner {
|
||||
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final SecurityProperties securityProperties;
|
||||
private final GatewayFallbackProperties fallbackProperties;
|
||||
|
||||
/**
|
||||
* 요청(payload)을 분석하여 실행해야 할 Tool의 계획을 생성합니다.
|
||||
*/
|
||||
public Object createPlan(Map<String, Object> payload, String tenantId) {
|
||||
log.info(" [Planner] 요청 분석 및 실행 계획 수립 시작");
|
||||
|
||||
// 1. 요청에서 호출하려는 툴 이름 추출 (JSON-RPC params.name)
|
||||
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
||||
String toolName = params != null ? (String) params.get("name") : null;
|
||||
|
||||
if (toolName == null || toolName.isEmpty()) {
|
||||
throw new IllegalArgumentException("요청에 toolName이 포함되어 있지 않습니다.");
|
||||
}
|
||||
|
||||
// 2. RedisRegistry에서 해당 툴의 메타데이터 조회
|
||||
// (실제로는 이 메타데이터가 실행 계획의 핵심이 됩니다)
|
||||
var toolMetadata = redisRegistryService.getToolByName(toolName);
|
||||
|
||||
if (toolMetadata == null) {
|
||||
log.warn(" [Planner] 등록되지 않은 툴 요청: {}. Fallback 라우팅 규칙을 확인합니다.", toolName);
|
||||
|
||||
String fallbackPodUrl = fallbackProperties.getDefaultUrl();
|
||||
if (fallbackProperties.getRoutes() != null) {
|
||||
for (Map.Entry<String, String> entry : fallbackProperties.getRoutes().entrySet()) {
|
||||
if (toolName.contains(entry.getKey())) {
|
||||
fallbackPodUrl = entry.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackPodUrl == null || fallbackPodUrl.isEmpty()) {
|
||||
log.error(" [Planner] Fallback 라우팅 대상이 아닙니다. 툴: {}", toolName);
|
||||
throw new RuntimeException("해당 툴(" + toolName + ")이 레지스트리에 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
log.info(" [Planner] Fallback 라우팅 매칭됨: {} -> {}", toolName, fallbackPodUrl);
|
||||
|
||||
toolMetadata = ToolMetadata.builder()
|
||||
.uid(toolName)
|
||||
.name(toolName)
|
||||
.integrationType("DIRECT")
|
||||
.podUrl(fallbackPodUrl)
|
||||
.build();
|
||||
}
|
||||
|
||||
// 2-1. [신규] 도메인 그룹핑 기반 권한 검증
|
||||
if (tenantId != null && toolMetadata.getCategoryKey() != null) {
|
||||
String normalizedTenantId = tenantId.toLowerCase();
|
||||
List<String> allowedDomains = securityProperties.getTenantDomains().get(normalizedTenantId);
|
||||
|
||||
// 만약 대소문자 변환 후에도 없으면 원래 값으로 한 번 더 시도 (하위 호환성)
|
||||
if (allowedDomains == null) {
|
||||
allowedDomains = securityProperties.getTenantDomains().get(tenantId);
|
||||
}
|
||||
|
||||
if (allowedDomains == null ||
|
||||
(!allowedDomains.contains("ALL") && !allowedDomains.contains(toolMetadata.getCategoryKey()))) {
|
||||
log.warn(" [Planner] 권한 거부 - Tenant: {}, Request Domain: {}", tenantId, toolMetadata.getCategoryKey());
|
||||
throw new SecurityException("해당 도메인(" + toolMetadata.getCategoryKey() + ")의 툴을 실행할 권한이 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
log.info(" [Planner] 툴 '{}'에 대한 실행 계획 수립 완료", toolName);
|
||||
|
||||
// 3. 수립된 계획(툴 메타데이터) 반환
|
||||
return toolMetadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.modelcontextprotocol.json.TypeRef;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.McpServerSession;
|
||||
import io.modelcontextprotocol.spec.McpServerTransport;
|
||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.GET;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.POST;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.accept;
|
||||
|
||||
@Slf4j
|
||||
public class CustomWebMvcSseServerTransportProvider implements McpServerTransportProvider {
|
||||
|
||||
private McpServerSession.Factory sessionFactory;
|
||||
private final String sseEndpoint;
|
||||
private final String messageEndpoint;
|
||||
private final Map<String, McpServerSession> sessions = new ConcurrentHashMap<>();
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public CustomWebMvcSseServerTransportProvider(String sseEndpoint, String messageEndpoint, ObjectMapper objectMapper) {
|
||||
this.sseEndpoint = sseEndpoint;
|
||||
this.messageEndpoint = messageEndpoint;
|
||||
this.objectMapper = objectMapper != null ? objectMapper : new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> closeGracefully() {
|
||||
return Mono.fromRunnable(() -> {
|
||||
sessions.values().forEach(session -> {
|
||||
try {
|
||||
session.closeGracefully().subscribe();
|
||||
} catch (Exception ignored) {}
|
||||
});
|
||||
sessions.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> notifyClients(String method, Object params) {
|
||||
return Mono.when(sessions.values().stream()
|
||||
.map(session -> session.sendNotification(method, params))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public org.springframework.web.servlet.mvc.method.annotation.SseEmitter handleSse() {
|
||||
if (sessionFactory == null) {
|
||||
throw new IllegalStateException("SessionFactory not configured");
|
||||
}
|
||||
|
||||
org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter = new org.springframework.web.servlet.mvc.method.annotation.SseEmitter(-1L);
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
|
||||
CustomMcpSessionTransport sessionTransport = new CustomMcpSessionTransport(emitter, sessionId);
|
||||
McpServerSession session = sessionFactory.create(sessionTransport);
|
||||
sessions.put(sessionId, session);
|
||||
|
||||
emitter.onCompletion(() -> sessions.remove(sessionId));
|
||||
emitter.onTimeout(() -> sessions.remove(sessionId));
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("endpoint").data(messageEndpoint + "?sessionId=" + sessionId));
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
public org.springframework.http.ResponseEntity<String> handleMessage(String sessionId, String body) {
|
||||
log.info("Received POST message for sessionId: " + sessionId + ", body: " + body);
|
||||
if (sessionId == null || !sessions.containsKey(sessionId)) {
|
||||
return org.springframework.http.ResponseEntity.badRequest().body("Missing or invalid sessionId");
|
||||
}
|
||||
|
||||
McpServerSession session = sessions.get(sessionId);
|
||||
try {
|
||||
java.util.Map<String, Object> map = objectMapper.readValue(body, new com.fasterxml.jackson.core.type.TypeReference<java.util.Map<String, Object>>() {});
|
||||
io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage message;
|
||||
|
||||
if (map.containsKey("id")) {
|
||||
if (map.containsKey("method")) {
|
||||
message = objectMapper.convertValue(map, io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest.class);
|
||||
} else {
|
||||
message = objectMapper.convertValue(map, io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse.class);
|
||||
}
|
||||
} else {
|
||||
message = objectMapper.convertValue(map, io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification.class);
|
||||
}
|
||||
log.info("Converted message type: " + message.getClass().getName());
|
||||
|
||||
session.handle(message).subscribe();
|
||||
log.info("Message sent to session handler");
|
||||
return org.springframework.http.ResponseEntity.ok().build();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to handle message", e);
|
||||
return org.springframework.http.ResponseEntity.status(500).body(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomMcpSessionTransport implements McpServerTransport {
|
||||
private final org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter;
|
||||
private final String sessionId;
|
||||
|
||||
public CustomMcpSessionTransport(org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter, String sessionId) {
|
||||
this.emitter = emitter;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
log.info("Sending message to SSE stream: " + message.getClass().getName());
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(message);
|
||||
log.info("Serialized message: " + json);
|
||||
emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event().name("message").data(json));
|
||||
log.info("Message successfully sent to SSE emitter");
|
||||
} catch (Exception e) {
|
||||
log.error("Error sending message to SSE emitter", e);
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> closeGracefully() {
|
||||
return Mono.fromRunnable(emitter::complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unmarshalFrom(Object object, TypeRef<T> typeRef) {
|
||||
return objectMapper.convertValue(object, objectMapper.constructType(typeRef.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
@RestController
|
||||
public class DynamicMcpController {
|
||||
|
||||
private final DynamicMcpServerManager manager;
|
||||
|
||||
public DynamicMcpController(DynamicMcpServerManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@GetMapping("/mcp/sse/{category}")
|
||||
public SseEmitter handleSse(@PathVariable("category") String category) {
|
||||
CustomWebMvcSseServerTransportProvider transport = manager.getTransport(category);
|
||||
if (transport == null) {
|
||||
throw new IllegalArgumentException("Unknown category: " + category);
|
||||
}
|
||||
return transport.handleSse();
|
||||
}
|
||||
|
||||
@PostMapping("/mcp/message/{category}")
|
||||
public ResponseEntity<String> handleMessage(
|
||||
@PathVariable("category") String category,
|
||||
@RequestParam("sessionId") String sessionId,
|
||||
@RequestBody String body) {
|
||||
|
||||
CustomWebMvcSseServerTransportProvider transport = manager.getTransport(category);
|
||||
if (transport == null) {
|
||||
return ResponseEntity.badRequest().body("Unknown category: " + category);
|
||||
}
|
||||
return transport.handleMessage(sessionId, body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||
|
||||
import io.modelcontextprotocol.server.McpServer;
|
||||
import io.modelcontextprotocol.server.McpSyncServer;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class DynamicMcpServerManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(DynamicMcpServerManager.class);
|
||||
|
||||
private final Map<String, McpSyncServer> categoryServers = new ConcurrentHashMap<>();
|
||||
private final Map<String, CustomWebMvcSseServerTransportProvider> categoryTransports = new ConcurrentHashMap<>();
|
||||
private final Map<String, Set<String>> managedToolNamesPerCategory = new ConcurrentHashMap<>();
|
||||
|
||||
private final RegistryMcpToolSpecificationFactory specificationFactory;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public DynamicMcpServerManager(RegistryMcpToolSpecificationFactory specificationFactory, ObjectMapper objectMapper) {
|
||||
this.specificationFactory = specificationFactory;
|
||||
this.objectMapper = objectMapper;
|
||||
|
||||
// Pre-initialize basic categories so their endpoints are always open
|
||||
getOrCreateServer("common");
|
||||
getOrCreateServer("hr");
|
||||
getOrCreateServer("payment");
|
||||
getOrCreateServer("sms");
|
||||
getOrCreateServer("email");
|
||||
getOrCreateServer("other");
|
||||
}
|
||||
|
||||
private McpSyncServer getOrCreateServer(String categoryKey) {
|
||||
String safeCategory = (categoryKey == null || categoryKey.trim().isEmpty()) ? "common" : categoryKey.toLowerCase();
|
||||
|
||||
return categoryServers.computeIfAbsent(safeCategory, key -> {
|
||||
log.info("Creating dynamic MCP Server for category: {}", key);
|
||||
String ssePath = "/mcp/sse/" + key;
|
||||
String msgPath = "/mcp/message/" + key;
|
||||
|
||||
CustomWebMvcSseServerTransportProvider transport = new CustomWebMvcSseServerTransportProvider(ssePath, msgPath, objectMapper);
|
||||
|
||||
McpSyncServer newServer = McpServer.sync(transport)
|
||||
.serverInfo("AXHUB-Gateway-" + key, "1.0.0")
|
||||
.capabilities(io.modelcontextprotocol.spec.McpSchema.ServerCapabilities.builder().tools(true).build())
|
||||
.build();
|
||||
|
||||
categoryTransports.put(key, transport);
|
||||
managedToolNamesPerCategory.put(key, ConcurrentHashMap.newKeySet());
|
||||
return newServer;
|
||||
});
|
||||
}
|
||||
|
||||
public void synchronizeCategory(String categoryKey, List<ToolMetadata> tools) {
|
||||
String safeCategory = (categoryKey == null || categoryKey.trim().isEmpty()) ? "common" : categoryKey.toLowerCase();
|
||||
McpSyncServer server = getOrCreateServer(safeCategory);
|
||||
|
||||
Set<String> managedToolNames = managedToolNamesPerCategory.get(safeCategory);
|
||||
Set<String> activeNames = tools.stream()
|
||||
.map(ToolMetadata::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> removedNames = managedToolNames.stream()
|
||||
.filter(name -> !activeNames.contains(name))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (String removedName : removedNames) {
|
||||
server.removeTool(removedName);
|
||||
managedToolNames.remove(removedName);
|
||||
log.info("[{}] Removed tool: {}", safeCategory, removedName);
|
||||
}
|
||||
|
||||
for (ToolMetadata entry : tools) {
|
||||
if (!managedToolNames.contains(entry.getName())) {
|
||||
server.addTool(specificationFactory.create(entry));
|
||||
managedToolNames.add(entry.getName());
|
||||
log.info("[{}] Added tool: {}", safeCategory, entry.getName());
|
||||
} else {
|
||||
server.removeTool(entry.getName());
|
||||
server.addTool(specificationFactory.create(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getKnownCategories() {
|
||||
return Collections.unmodifiableSet(categoryServers.keySet());
|
||||
}
|
||||
|
||||
public CustomWebMvcSseServerTransportProvider getTransport(String categoryKey) {
|
||||
String safeCategory = (categoryKey == null || categoryKey.trim().isEmpty()) ? "common" : categoryKey.toLowerCase();
|
||||
return categoryTransports.get(safeCategory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.service.ExecuteService;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.tool.result.ToolExecutionResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Registry에 등록된 실제 Tool 메타데이터를 MCP 표준 Tool specification으로 변환합니다.
|
||||
*/
|
||||
@Component
|
||||
public class RegistryMcpToolSpecificationFactory {
|
||||
private final ExecuteService executeService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public RegistryMcpToolSpecificationFactory(ExecuteService executeService, ObjectMapper objectMapper) {
|
||||
this.executeService = executeService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry Entry 하나를 MCP SDK의 stateless sync Tool specification으로 변환합니다.
|
||||
*/
|
||||
public McpServerFeatures.SyncToolSpecification create(ToolMetadata entry) {
|
||||
McpSchema.Tool tool = McpSchema.Tool.builder()
|
||||
.name(entry.getName())
|
||||
.description(description(entry))
|
||||
.inputSchema(inputSchema(entry))
|
||||
.build();
|
||||
|
||||
return McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(tool)
|
||||
.callHandler((context, request) -> execute(entry, request))
|
||||
.build();
|
||||
}
|
||||
|
||||
private McpSchema.CallToolResult execute(ToolMetadata entry, McpSchema.CallToolRequest request) {
|
||||
try {
|
||||
// Build legacy JSON-RPC payload format expected by ExecuteService
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("jsonrpc", "2.0");
|
||||
payload.put("method", "tools/call");
|
||||
payload.put("id", UUID.randomUUID().toString());
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("name", entry.getName());
|
||||
params.put("arguments", request.arguments());
|
||||
payload.put("params", params);
|
||||
|
||||
// Execute through ExecuteService
|
||||
Object rawResult = executeService.execute(payload, "system");
|
||||
|
||||
// Convert JSON-RPC response back to McpSchema.CallToolResult
|
||||
return toCallToolResult(rawResult);
|
||||
} catch (Exception error) {
|
||||
return McpSchema.CallToolResult.builder()
|
||||
.addTextContent("Tool 실행 중 내부 오류가 발생했습니다: " + error.getMessage())
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private McpSchema.CallToolResult toCallToolResult(Object rawResult) {
|
||||
if (rawResult instanceof ToolExecutionResult result) {
|
||||
McpSchema.CallToolResult.Builder builder = McpSchema.CallToolResult.builder()
|
||||
.isError(result.isError())
|
||||
.meta(result.metadata());
|
||||
|
||||
if (result.content() == null || result.content().isEmpty()) {
|
||||
builder.addTextContent("");
|
||||
} else {
|
||||
for (ToolExecutionResult.ContentItem item : result.content()) {
|
||||
if ("text".equals(item.type())) {
|
||||
builder.addTextContent(item.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.structuredContent() != null) {
|
||||
builder.structuredContent(result.structuredContent());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// Fallback for old map format or unexpected types
|
||||
try {
|
||||
Map<String, Object> resultMap = objectMapper.convertValue(rawResult, new TypeReference<Map<String, Object>>() {});
|
||||
if (resultMap.containsKey("resultType") || resultMap.containsKey("structuredContent")) {
|
||||
ToolExecutionResult result = objectMapper.convertValue(rawResult, ToolExecutionResult.class);
|
||||
return toCallToolResult(result);
|
||||
}
|
||||
|
||||
Object innerResult = resultMap.containsKey("result") ? resultMap.get("result") : resultMap;
|
||||
|
||||
McpSchema.CallToolResult.Builder builder = McpSchema.CallToolResult.builder();
|
||||
builder.isError(resultMap.containsKey("error"));
|
||||
builder.addTextContent(objectMapper.writeValueAsString(innerResult));
|
||||
return builder.build();
|
||||
} catch (Exception e) {
|
||||
return McpSchema.CallToolResult.builder()
|
||||
.addTextContent(String.valueOf(rawResult))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> inputSchema(ToolMetadata entry) {
|
||||
if (entry.getParametersSchema() != null) {
|
||||
return entry.getParametersSchema();
|
||||
}
|
||||
|
||||
Map<String, Object> schema = new LinkedHashMap<>();
|
||||
schema.put("type", "object");
|
||||
schema.put("properties", new HashMap<>());
|
||||
schema.put("additionalProperties", false);
|
||||
return schema;
|
||||
}
|
||||
|
||||
private String description(ToolMetadata entry) {
|
||||
return entry.getDescription() == null || entry.getDescription().isBlank()
|
||||
? entry.getName() + " Tool"
|
||||
: entry.getDescription();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.sync;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.dto.ToolMetadata;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.registry.RedisRegistryService;
|
||||
import io.modelcontextprotocol.server.McpSyncServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.mcp.gateway.sync
|
||||
* @className RegistryMcpToolSynchronizer
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
public class RegistryMcpToolSynchronizer {
|
||||
private static final Logger log = LoggerFactory.getLogger(RegistryMcpToolSynchronizer.class);
|
||||
|
||||
private final ObjectProvider<McpSyncServer> mcpServerProvider;
|
||||
private final DynamicMcpServerManager dynamicMcpServerManager;
|
||||
private final RedisRegistryService redisRegistryService;
|
||||
private final RegistryMcpToolSpecificationFactory specificationFactory;
|
||||
private final Set<String> managedToolNames = new LinkedHashSet<>();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
public RegistryMcpToolSynchronizer(ObjectProvider<McpSyncServer> mcpServerProvider,
|
||||
DynamicMcpServerManager dynamicMcpServerManager,
|
||||
RedisRegistryService redisRegistryService,
|
||||
RegistryMcpToolSpecificationFactory specificationFactory) {
|
||||
this.mcpServerProvider = mcpServerProvider;
|
||||
this.dynamicMcpServerManager = dynamicMcpServerManager;
|
||||
this.redisRegistryService = redisRegistryService;
|
||||
this.specificationFactory = specificationFactory;
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void synchronizeOnStartup() {
|
||||
synchronize();
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelayString = "${mcp.tool-registry-sync-interval-millis:5000}")
|
||||
public void synchronizeScheduled() {
|
||||
synchronize();
|
||||
}
|
||||
|
||||
public void synchronize() {
|
||||
lock.lock();
|
||||
try {
|
||||
List<ToolMetadata> activeEntries = redisRegistryService.getAllTools()
|
||||
.stream().filter(ToolMetadata::getVisible).collect(Collectors.toList());
|
||||
|
||||
// 1. Dynamic MCP Server 동기화 (카테고리별)
|
||||
Map<String, List<ToolMetadata>> toolsByCategory = activeEntries.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
t -> (t.getCategoryKey() == null || t.getCategoryKey().trim().isEmpty()) ? "common" : t.getCategoryKey()
|
||||
));
|
||||
|
||||
Set<String> allKnownCategories = new LinkedHashSet<>(dynamicMcpServerManager.getKnownCategories());
|
||||
allKnownCategories.addAll(toolsByCategory.keySet());
|
||||
|
||||
for (String category : allKnownCategories) {
|
||||
dynamicMcpServerManager.synchronizeCategory(category, toolsByCategory.getOrDefault(category, List.of()));
|
||||
}
|
||||
|
||||
// 2. Global MCP Server 동기화 (기존 하위 호환)
|
||||
McpSyncServer server = mcpServerProvider.getIfAvailable();
|
||||
if (server != null) {
|
||||
Set<String> activeNames = activeEntries.stream()
|
||||
.map(ToolMetadata::getName)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
Set<String> removedNames = new LinkedHashSet<>(managedToolNames);
|
||||
removedNames.removeAll(activeNames);
|
||||
for (String removedName : removedNames) {
|
||||
server.removeTool(removedName);
|
||||
managedToolNames.remove(removedName);
|
||||
log.info("Removed MCP registry tool (Global). tool={}", removedName);
|
||||
}
|
||||
|
||||
for (ToolMetadata entry : activeEntries) {
|
||||
if (managedToolNames.contains(entry.getName())) {
|
||||
server.removeTool(entry.getName());
|
||||
}
|
||||
server.addTool(specificationFactory.create(entry));
|
||||
managedToolNames.add(entry.getName());
|
||||
}
|
||||
}
|
||||
} catch (Exception error) {
|
||||
log.warn("MCP registry tool synchronization failed. message={}", error.getMessage());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.AgentResponseBudgetProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
@Service
|
||||
public class AgentResponseBudgetService {
|
||||
private final AgentResponseBudgetProperties properties;
|
||||
private final ObjectMapper json;
|
||||
private final SensitiveDataMasker masker;
|
||||
|
||||
public AgentResponseBudgetService(AgentResponseBudgetProperties properties, ObjectMapper json, SensitiveDataMasker masker) {
|
||||
this.properties = properties;
|
||||
this.json = json;
|
||||
this.masker = masker;
|
||||
}
|
||||
|
||||
public ObjectNode apply(ObjectNode response) {
|
||||
ArrayNode originalItems = response.path("previewItems").isArray()
|
||||
? (ArrayNode) response.path("previewItems")
|
||||
: json.createArrayNode();
|
||||
int originalCount = originalItems.size();
|
||||
BudgetStats stats = new BudgetStats();
|
||||
ArrayNode budgetItems = json.createArrayNode();
|
||||
|
||||
for (JsonNode item : originalItems) {
|
||||
if (budgetItems.size() >= properties.maxPreviewItems()) {
|
||||
stats.omittedItems++;
|
||||
stats.limited = true;
|
||||
continue;
|
||||
}
|
||||
JsonNode budgetItem = budgetItem(item, stats);
|
||||
if (jsonBytes(budgetItem) > properties.maxItemBytes()) {
|
||||
budgetItem = fallbackTruncatedItem(budgetItem);
|
||||
stats.truncatedItems++;
|
||||
stats.limited = true;
|
||||
}
|
||||
budgetItems.add(budgetItem);
|
||||
}
|
||||
|
||||
ObjectNode budgeted = copyWithout(response, "previewItems", "budget", "summary");
|
||||
budgeted.set("previewItems", budgetItems);
|
||||
if (properties.includeSummary()) {
|
||||
budgeted.put("summary", summary(budgeted, originalCount, budgetItems.size()));
|
||||
}
|
||||
|
||||
while (jsonBytes(budgeted) > properties.maxTotalBytes() && budgetItems.size() > 0) {
|
||||
budgetItems.remove(budgetItems.size() - 1);
|
||||
stats.omittedItems++;
|
||||
stats.limited = true;
|
||||
budgeted.set("previewItems", budgetItems);
|
||||
if (properties.includeSummary()) {
|
||||
budgeted.put("summary", summary(budgeted, originalCount, budgetItems.size()));
|
||||
}
|
||||
}
|
||||
|
||||
ObjectNode budget = json.createObjectNode();
|
||||
updateBudget(budget, stats, originalCount, budgetItems.size());
|
||||
budgeted.put("previewCount", budgetItems.size());
|
||||
budgeted.set("budget", budget);
|
||||
while (jsonBytes(budgeted) > properties.maxTotalBytes() && budgetItems.size() > 0) {
|
||||
budgetItems.remove(budgetItems.size() - 1);
|
||||
stats.omittedItems++;
|
||||
stats.limited = true;
|
||||
budgeted.set("previewItems", budgetItems);
|
||||
budgeted.put("previewCount", budgetItems.size());
|
||||
if (properties.includeSummary()) {
|
||||
budgeted.put("summary", summary(budgeted, originalCount, budgetItems.size()));
|
||||
}
|
||||
updateBudget(budget, stats, originalCount, budgetItems.size());
|
||||
budgeted.set("budget", budget);
|
||||
}
|
||||
return budgeted;
|
||||
}
|
||||
|
||||
private void updateBudget(ObjectNode budget, BudgetStats stats, int originalCount, int previewCount) {
|
||||
budget.put("limited", stats.limited || stats.omittedItems > 0 || stats.truncatedItems > 0);
|
||||
budget.put("maxPreviewItems", properties.maxPreviewItems());
|
||||
budget.put("maxFieldsPerItem", properties.maxFieldsPerItem());
|
||||
budget.put("maxItemBytes", properties.maxItemBytes());
|
||||
budget.put("maxTotalBytes", properties.maxTotalBytes());
|
||||
budget.put("originalPreviewCount", originalCount);
|
||||
budget.put("previewCount", previewCount);
|
||||
budget.put("omittedItems", stats.omittedItems);
|
||||
budget.put("truncatedItems", stats.truncatedItems);
|
||||
}
|
||||
|
||||
private JsonNode budgetItem(JsonNode item, BudgetStats stats) {
|
||||
JsonNode masked = masker.mask(item);
|
||||
if (!masked.isObject()) {
|
||||
return truncateByBytes(masked, stats);
|
||||
}
|
||||
ObjectNode limited = json.createObjectNode();
|
||||
int copied = 0;
|
||||
int originalFields = 0;
|
||||
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = masked.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> entry = fields.next();
|
||||
originalFields++;
|
||||
if (copied >= properties.maxFieldsPerItem()) {
|
||||
continue;
|
||||
}
|
||||
limited.set(entry.getKey(), entry.getValue());
|
||||
copied++;
|
||||
}
|
||||
|
||||
if (originalFields > copied) {
|
||||
limited.put("truncated", true);
|
||||
limited.put("omittedFieldCount", originalFields - copied);
|
||||
stats.truncatedItems++;
|
||||
stats.limited = true;
|
||||
}
|
||||
return truncateByBytes(limited, stats);
|
||||
}
|
||||
|
||||
private JsonNode truncateByBytes(JsonNode item, BudgetStats stats) {
|
||||
if (jsonBytes(item) <= properties.maxItemBytes()) {
|
||||
return item;
|
||||
}
|
||||
if (!item.isObject()) {
|
||||
stats.truncatedItems++;
|
||||
stats.limited = true;
|
||||
ObjectNode truncated = json.createObjectNode();
|
||||
truncated.put("truncated", true);
|
||||
truncated.put("originalBytes", jsonBytes(item));
|
||||
truncated.put("maxItemBytes", properties.maxItemBytes());
|
||||
return truncated;
|
||||
}
|
||||
ObjectNode source = (ObjectNode) item;
|
||||
ObjectNode limited = json.createObjectNode();
|
||||
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = source.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> entry = fields.next();
|
||||
JsonNode value = entry.getValue();
|
||||
if (value.isTextual()) {
|
||||
limited.put(entry.getKey(), truncateText(value.asText()));
|
||||
} else {
|
||||
limited.set(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
limited.put("truncated", true);
|
||||
limited.put("originalBytes", jsonBytes(item));
|
||||
limited.put("maxItemBytes", properties.maxItemBytes());
|
||||
stats.truncatedItems++;
|
||||
stats.limited = true;
|
||||
if (jsonBytes(limited) <= properties.maxItemBytes()) {
|
||||
return limited;
|
||||
}
|
||||
return fallbackTruncatedItem(item);
|
||||
}
|
||||
|
||||
private ObjectNode fallbackTruncatedItem(JsonNode item) {
|
||||
ObjectNode fallback = json.createObjectNode();
|
||||
fallback.put("truncated", true);
|
||||
fallback.put("originalBytes", jsonBytes(item));
|
||||
fallback.put("maxItemBytes", properties.maxItemBytes());
|
||||
if (item.isObject()) {
|
||||
ArrayNode fieldNames = json.createArrayNode();
|
||||
java.util.Iterator<String> fieldNamesIter = item.fieldNames();
|
||||
while (fieldNamesIter.hasNext()) {
|
||||
fieldNames.add(fieldNamesIter.next());
|
||||
}
|
||||
fallback.set("fieldNames", fieldNames);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private String truncateText(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
int maxChars = Math.min(value.length(), 256);
|
||||
String truncated = value.substring(0, maxChars);
|
||||
while (truncated.length() > 16 && truncated.getBytes(java.nio.charset.StandardCharsets.UTF_8).length > properties.maxItemBytes() / 2) {
|
||||
truncated = truncated.substring(0, truncated.length() / 2);
|
||||
}
|
||||
return truncated + "...";
|
||||
}
|
||||
|
||||
private ObjectNode copyWithout(ObjectNode source, String... excludedFields) {
|
||||
ObjectNode copy = json.createObjectNode();
|
||||
|
||||
java.util.Iterator<java.util.Map.Entry<String, JsonNode>> fields = source.fields();
|
||||
while (fields.hasNext()) {
|
||||
java.util.Map.Entry<String, JsonNode> entry = fields.next();
|
||||
if (!excluded(entry.getKey(), excludedFields)) {
|
||||
copy.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private boolean excluded(String field, String[] excludedFields) {
|
||||
for (String excluded : excludedFields) {
|
||||
if (excluded.equals(field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String summary(ObjectNode response, int originalCount, int previewCount) {
|
||||
long totalCount = response.path("totalCount").asLong(originalCount);
|
||||
return "Returned " + previewCount + " preview item(s) out of " + totalCount + " total result(s).";
|
||||
}
|
||||
|
||||
private long jsonBytes(JsonNode node) {
|
||||
try {
|
||||
return json.writeValueAsBytes(node).length;
|
||||
} catch (Exception error) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BudgetStats {
|
||||
private boolean limited;
|
||||
private int omittedItems;
|
||||
private int truncatedItems;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
@Service
|
||||
public class LargeToolResponseService {
|
||||
private final McpGatewayProperties properties;
|
||||
private final ObjectMapper json;
|
||||
private final SensitiveDataMasker masker;
|
||||
private final AgentResponseBudgetService agentBudget;
|
||||
|
||||
public LargeToolResponseService(McpGatewayProperties properties,
|
||||
ObjectMapper json,
|
||||
SensitiveDataMasker masker,
|
||||
AgentResponseBudgetService agentBudget) {
|
||||
this.properties = properties;
|
||||
this.json = json;
|
||||
this.masker = masker;
|
||||
this.agentBudget = agentBudget;
|
||||
}
|
||||
|
||||
public Collector newCollector(String toolName, String requestId) {
|
||||
return new Collector(toolName, requestId);
|
||||
}
|
||||
|
||||
public ObjectNode tooLargeResult(String toolName, String requestId, long receivedBytes, String nextCursor) {
|
||||
ObjectNode response = basePartialResponse(toolName, requestId);
|
||||
response.put("failureType", FailureType.TOO_LARGE_RESULT.name());
|
||||
response.put("errorCode", "TOOL_RESULT_TOO_LARGE");
|
||||
response.put("message", "Tool 응답이 허용 크기를 초과했습니다. 원문 body는 저장하지 않았고 cursor/resultRef 기반 재조회가 필요합니다.");
|
||||
response.put("receivedBytes", receivedBytes);
|
||||
response.put("maxResponseBytesFromTool", properties.maxResponseBytesFromTool());
|
||||
response.put("hasMore", true);
|
||||
response.put("nextCursor", safe(nextCursor));
|
||||
response.put("resultRef", resultRef(toolName, requestId, nextCursor));
|
||||
response.put("truncated", true);
|
||||
response.put("omittedCount", 0);
|
||||
response.set("previewItems", json.createArrayNode());
|
||||
response.set("streamEvents", streamEvents(toolName, 0, 0, true, nextCursor));
|
||||
attachPageInfoAndStructuredContent(response);
|
||||
return agentBudget.apply(response);
|
||||
}
|
||||
|
||||
private ObjectNode basePartialResponse(String toolName, String requestId) {
|
||||
ObjectNode response = json.createObjectNode();
|
||||
response.put("success", true);
|
||||
response.put("toolName", toolName);
|
||||
response.put("requestId", requestId);
|
||||
response.put("status", "PARTIAL_RESULT");
|
||||
response.put("pageSize", pageSize());
|
||||
response.put("pageCount", 0);
|
||||
response.put("returnedCount", 0);
|
||||
response.put("totalCount", 0);
|
||||
response.put("hasMore", false);
|
||||
response.put("nextCursor", "");
|
||||
response.put("truncated", false);
|
||||
response.put("omittedCount", 0);
|
||||
response.put("maxStreamBytes", properties.maxStreamBytes());
|
||||
response.put("maxItemBytes", properties.largeResponseMaxItemBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
private ArrayNode streamEvents(String toolName, int pageCount, int returnedCount, boolean hasMore, String nextCursor) {
|
||||
ArrayNode events = json.createArrayNode();
|
||||
ObjectNode start = json.createObjectNode();
|
||||
start.put("type", "start");
|
||||
start.put("tool", toolName);
|
||||
events.add(start);
|
||||
|
||||
ObjectNode summary = json.createObjectNode();
|
||||
summary.put("type", "summary");
|
||||
summary.put("pageCount", pageCount);
|
||||
summary.put("returnedCount", returnedCount);
|
||||
summary.put("message", returnedCount + "건의 preview를 생성했습니다.");
|
||||
events.add(summary);
|
||||
|
||||
ObjectNode done = json.createObjectNode();
|
||||
done.put("type", "done");
|
||||
done.put("hasMore", hasMore);
|
||||
done.put("nextCursor", safe(nextCursor));
|
||||
events.add(done);
|
||||
return events;
|
||||
}
|
||||
|
||||
private String resultRef(String toolName, String requestId, String nextCursor) {
|
||||
if (nextCursor == null || nextCursor.isBlank()) {
|
||||
return "tool-result:" + toolName + ":" + requestId;
|
||||
}
|
||||
return "tool-result:" + toolName + ":" + requestId + ":cursor:" + nextCursor;
|
||||
}
|
||||
|
||||
private int pageSize() {
|
||||
return Math.min(properties.defaultPageSize(), properties.maxPageSize());
|
||||
}
|
||||
|
||||
private String safe(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
public class Collector {
|
||||
private final String toolName;
|
||||
private final String requestId;
|
||||
private final Instant startedAt = Instant.now();
|
||||
private final ArrayNode previewItems = json.createArrayNode();
|
||||
private final ArrayNode streamEvents = json.createArrayNode();
|
||||
private int pageCount;
|
||||
private int returnedCount;
|
||||
private long totalCount;
|
||||
private boolean paginated;
|
||||
private boolean hasMore;
|
||||
private String nextCursor = "";
|
||||
private String stopReason = "";
|
||||
private JsonNode normalData;
|
||||
private boolean truncated;
|
||||
private int omittedCount;
|
||||
|
||||
private Collector(String toolName, String requestId) {
|
||||
this.toolName = toolName;
|
||||
this.requestId = requestId;
|
||||
ObjectNode start = json.createObjectNode();
|
||||
start.put("type", "start");
|
||||
start.put("tool", toolName);
|
||||
streamEvents.add(start);
|
||||
}
|
||||
|
||||
public void accept(JsonNode data) {
|
||||
if (data == null || data.isMissingNode() || data.isNull()) {
|
||||
throw new ToolExecutionException(FailureType.BUSINESS_ERROR, "Tool response data is empty. tool=" + toolName);
|
||||
}
|
||||
Page page = pageFrom(data);
|
||||
if (!page.paginated() && pageCount == 0 && count(page.items()) <= pageSize()) {
|
||||
JsonNode masked = masker.mask(data);
|
||||
normalData = masked;
|
||||
pageCount = 1;
|
||||
returnedCount = count(page.items());
|
||||
totalCount = returnedCount;
|
||||
return;
|
||||
}
|
||||
|
||||
paginated = true;
|
||||
pageCount++;
|
||||
totalCount = page.totalCount() >= 0 ? page.totalCount() : totalCount;
|
||||
hasMore = page.hasMore();
|
||||
nextCursor = page.nextCursor();
|
||||
|
||||
int pageItemCount = 0;
|
||||
for (JsonNode item : page.items()) {
|
||||
pageItemCount++;
|
||||
JsonNode preview = previewItem(item);
|
||||
if (previewItems.size() < maxPreviewItems() && canAddPreviewItem(preview)) {
|
||||
previewItems.add(preview);
|
||||
returnedCount++;
|
||||
} else {
|
||||
omittedCount++;
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectNode summary = json.createObjectNode();
|
||||
summary.put("type", "summary");
|
||||
summary.put("page", pageCount);
|
||||
summary.put("count", pageItemCount);
|
||||
summary.put("returnedCount", returnedCount);
|
||||
summary.put("omittedCount", omittedCount);
|
||||
summary.put("nextCursor", nextCursor());
|
||||
summary.put("message", returnedCount + " items previewed");
|
||||
addStreamEvent(summary);
|
||||
|
||||
if (pageCount >= properties.maxPagesPerCall() && hasMore) {
|
||||
stopReason = "MAX_PAGES_PER_CALL";
|
||||
truncated = true;
|
||||
}
|
||||
if (Duration.between(startedAt, Instant.now()).toSeconds() >= properties.maxDurationSeconds() && hasMore) {
|
||||
stopReason = "MAX_DURATION_SECONDS";
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldFetchNextPage() {
|
||||
return paginated
|
||||
&& hasMore
|
||||
&& nextCursor != null
|
||||
&& !nextCursor.isBlank()
|
||||
&& pageCount < properties.maxPagesPerCall()
|
||||
&& Duration.between(startedAt, Instant.now()).toSeconds() < properties.maxDurationSeconds()
|
||||
&& currentResponseBytes() < properties.maxStreamBytes();
|
||||
}
|
||||
|
||||
public String nextCursor() {
|
||||
return safe(nextCursor);
|
||||
}
|
||||
|
||||
public int pageSize() {
|
||||
return LargeToolResponseService.this.pageSize();
|
||||
}
|
||||
|
||||
public ObjectNode finish() {
|
||||
if (normalData != null) {
|
||||
ObjectNode wrap = json.createObjectNode();
|
||||
wrap.put("success", true);
|
||||
wrap.put("toolName", toolName);
|
||||
wrap.put("requestId", requestId);
|
||||
wrap.set("data", normalData);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
ObjectNode done = json.createObjectNode();
|
||||
done.put("type", "done");
|
||||
done.put("totalCount", totalCount);
|
||||
done.put("hasMore", hasMore);
|
||||
done.put("nextCursor", nextCursor());
|
||||
addStreamEvent(done);
|
||||
|
||||
ObjectNode response = basePartialResponse(toolName, requestId);
|
||||
response.put("pageCount", pageCount);
|
||||
response.put("returnedCount", returnedCount);
|
||||
response.put("totalCount", totalCount);
|
||||
response.put("hasMore", hasMore);
|
||||
response.put("nextCursor", nextCursor());
|
||||
response.put("resultRef", resultRef(toolName, requestId, nextCursor()));
|
||||
response.put("truncated", truncated);
|
||||
response.put("omittedCount", omittedCount);
|
||||
response.put("message", stopReason.isBlank()
|
||||
? "대량 결과라 일부 preview만 반환했습니다."
|
||||
: "대량 결과라 " + stopReason + " 조건에서 중단하고 일부 preview만 반환했습니다.");
|
||||
response.set("previewItems", previewItems);
|
||||
response.set("streamEvents", streamEvents);
|
||||
attachPageInfoAndStructuredContent(response);
|
||||
return agentBudget.apply(response);
|
||||
}
|
||||
|
||||
private Page pageFrom(JsonNode data) {
|
||||
if (data.isObject() && data.path("items").isArray()) {
|
||||
return new Page(true, data.path("items"), data.path("nextCursor").asText(""),
|
||||
data.path("hasMore").asBoolean(false), data.path("totalCount").asLong(-1));
|
||||
}
|
||||
if (data.isArray()) {
|
||||
return new Page(false, data, "", false, count(data));
|
||||
}
|
||||
ArrayNode one = json.createArrayNode();
|
||||
one.add(data);
|
||||
return new Page(false, one, "", false, 1);
|
||||
}
|
||||
|
||||
private JsonNode previewItem(JsonNode item) {
|
||||
JsonNode masked = masker.mask(item);
|
||||
long itemBytes = jsonBytes(masked);
|
||||
if (itemBytes <= properties.largeResponseMaxItemBytes()) {
|
||||
return masked;
|
||||
}
|
||||
truncated = true;
|
||||
ObjectNode preview = json.createObjectNode();
|
||||
preview.put("truncated", true);
|
||||
preview.put("originalBytes", itemBytes);
|
||||
preview.put("maxItemBytes", properties.largeResponseMaxItemBytes());
|
||||
if (masked.isObject()) {
|
||||
ArrayNode fieldNames = json.createArrayNode();
|
||||
java.util.Iterator<String> fieldNamesIter = masked.fieldNames();
|
||||
while (fieldNamesIter.hasNext()) {
|
||||
fieldNames.add(fieldNamesIter.next());
|
||||
}
|
||||
preview.set("fieldNames", fieldNames);
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
private boolean canAddPreviewItem(JsonNode item) {
|
||||
return jsonBytes(item) <= properties.largeResponseMaxItemBytes()
|
||||
&& currentResponseBytes() + jsonBytes(item) <= properties.maxStreamBytes();
|
||||
}
|
||||
|
||||
private void addStreamEvent(ObjectNode event) {
|
||||
if (currentResponseBytes() + jsonBytes(event) <= properties.maxStreamBytes()) {
|
||||
streamEvents.add(event);
|
||||
return;
|
||||
}
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
private int maxPreviewItems() {
|
||||
return Math.min(pageSize() * properties.maxPagesPerCall(), properties.largeResponseMaxPreviewItems());
|
||||
}
|
||||
|
||||
private long currentResponseBytes() {
|
||||
ObjectNode estimate = json.createObjectNode();
|
||||
estimate.set("previewItems", previewItems);
|
||||
estimate.set("streamEvents", streamEvents);
|
||||
return jsonBytes(estimate);
|
||||
}
|
||||
|
||||
private long jsonBytes(JsonNode node) {
|
||||
try {
|
||||
return json.writeValueAsBytes(node).length;
|
||||
} catch (Exception error) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private int count(JsonNode node) {
|
||||
int count = 0;
|
||||
java.util.Iterator<JsonNode> iter = node.elements();
|
||||
while(iter.hasNext()) {
|
||||
iter.next();
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private record Page(boolean paginated, JsonNode items, String nextCursor, boolean hasMore, long totalCount) {}
|
||||
|
||||
private void attachPageInfoAndStructuredContent(ObjectNode response) {
|
||||
ObjectNode pageInfo = json.createObjectNode();
|
||||
pageInfo.put("pageSize", response.path("pageSize").asInt(pageSize()));
|
||||
pageInfo.put("pageCount", response.path("pageCount").asInt(0));
|
||||
pageInfo.put("returnedCount", response.path("returnedCount").asInt(0));
|
||||
pageInfo.put("totalCount", response.path("totalCount").asLong(0));
|
||||
pageInfo.put("hasMore", response.path("hasMore").asBoolean(false));
|
||||
pageInfo.put("nextCursor", response.path("nextCursor").asText(""));
|
||||
response.set("pageInfo", pageInfo);
|
||||
|
||||
ObjectNode summary = json.createObjectNode();
|
||||
summary.put("status", response.path("status").asText("PARTIAL_RESULT"));
|
||||
summary.put("toolName", response.path("toolName").asText(""));
|
||||
summary.put("message", response.path("message").asText(""));
|
||||
summary.put("truncated", response.path("truncated").asBoolean(false));
|
||||
summary.put("omittedCount", response.path("omittedCount").asInt(0));
|
||||
summary.put("resultRef", response.path("resultRef").asText(""));
|
||||
|
||||
ObjectNode structuredContent = json.createObjectNode();
|
||||
structuredContent.set("summary", summary);
|
||||
structuredContent.set("pageInfo", pageInfo.deepCopy());
|
||||
structuredContent.set("previewItems", response.path("previewItems").deepCopy());
|
||||
response.set("structuredContent", structuredContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.tool.large;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.config.McpGatewayProperties;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* Tool 서버로 전달할 pagination 요청값을 MCP 정책 기준으로 검증하고 보정합니다.
|
||||
*/
|
||||
@Component
|
||||
public class PaginationRequestValidator {
|
||||
private static final int MAX_CURSOR_LENGTH = 2048;
|
||||
|
||||
private final McpGatewayProperties properties;
|
||||
private final ObjectMapper json;
|
||||
|
||||
public PaginationRequestValidator(McpGatewayProperties properties, ObjectMapper json) {
|
||||
this.properties = properties;
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* pageSize/cursor를 검증한 뒤 Tool 서버에 넘길 안전한 arguments 복사본을 만듭니다.
|
||||
*/
|
||||
public ObjectNode normalize(ObjectNode arguments) {
|
||||
try {
|
||||
ObjectNode normalized = arguments == null
|
||||
? json.createObjectNode()
|
||||
: (ObjectNode) json.readTree(json.writeValueAsString(arguments));
|
||||
normalizePageSize(normalized);
|
||||
validateCursor(normalized);
|
||||
return normalized;
|
||||
} catch (ToolExecutionException error) {
|
||||
throw error;
|
||||
} catch (Exception error) {
|
||||
throw standardError("INVALID_PAGINATION_REQUEST",
|
||||
"Pagination 요청값을 검증하는 중 오류가 발생했습니다.",
|
||||
detail("cause", error.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizePageSize(ObjectNode normalized) {
|
||||
int pageSize = normalized.has("pageSize")
|
||||
? normalized.path("pageSize").asInt(-1)
|
||||
: properties.defaultPageSize();
|
||||
if (pageSize < 1) {
|
||||
throw standardError("INVALID_PAGINATION_REQUEST",
|
||||
"pageSize는 1 이상이어야 합니다.",
|
||||
detail("requestedPageSize", pageSize));
|
||||
}
|
||||
if (pageSize > properties.maxPageSize()) {
|
||||
ObjectNode detail = detail("requestedPageSize", pageSize);
|
||||
detail.put("maxPageSize", properties.maxPageSize());
|
||||
detail.put("suggestedPageSize", properties.defaultPageSize());
|
||||
throw standardError("PAGE_SIZE_EXCEEDED",
|
||||
"요청한 pageSize가 MCP 최대 허용값을 초과했습니다.",
|
||||
detail);
|
||||
}
|
||||
normalized.put("pageSize", pageSize);
|
||||
}
|
||||
|
||||
private void validateCursor(ObjectNode normalized) {
|
||||
if (!normalized.has("cursor")) {
|
||||
return;
|
||||
}
|
||||
String cursor = normalized.path("cursor").asText("");
|
||||
if (cursor.length() > MAX_CURSOR_LENGTH) {
|
||||
ObjectNode detail = detail("cursorLength", cursor.length());
|
||||
detail.put("maxCursorLength", MAX_CURSOR_LENGTH);
|
||||
throw standardError("INVALID_CURSOR",
|
||||
"cursor 값이 너무 깁니다.",
|
||||
detail);
|
||||
}
|
||||
}
|
||||
|
||||
private ToolExecutionException standardError(String code, String message, ObjectNode detail) {
|
||||
ObjectNode error = json.createObjectNode();
|
||||
error.put("isError", true);
|
||||
error.put("errorCode", code);
|
||||
error.put("message", message);
|
||||
error.set("details", detail);
|
||||
try {
|
||||
return new ToolExecutionException(FailureType.CLIENT_ERROR, json.writeValueAsString(error));
|
||||
} catch (Exception serializeError) {
|
||||
return new ToolExecutionException(FailureType.CLIENT_ERROR, code + ": " + message);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectNode detail(String name, Object value) {
|
||||
ObjectNode detail = json.createObjectNode();
|
||||
if (value instanceof Number number) {
|
||||
detail.put(name, number.longValue());
|
||||
} else {
|
||||
detail.put(name, value == null ? "" : String.valueOf(value));
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.tool.result;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP Tool 실행 결과를 text 요약과 structuredContent로 분리해서 표현합니다.
|
||||
*/
|
||||
public record ToolExecutionResult(
|
||||
ResultType resultType,
|
||||
List<ContentItem> content,
|
||||
JsonNode structuredContent,
|
||||
List<ResourceLink> resourceLinks,
|
||||
Map<String, Object> metadata,
|
||||
boolean isError,
|
||||
boolean truncated,
|
||||
long originalSizeBytes
|
||||
) {
|
||||
public record ContentItem(String type, String text) {
|
||||
}
|
||||
|
||||
public record ResourceLink(String uri, String name, String mimeType, long sizeBytes) {
|
||||
}
|
||||
|
||||
public enum ResultType {
|
||||
JSON_OBJECT,
|
||||
JSON_ARRAY,
|
||||
PLAIN_TEXT,
|
||||
BINARY_FILE,
|
||||
EMPTY,
|
||||
ERROR
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.tool.result;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.guardrail.SensitiveDataMasker;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tool 서버 원시 응답을 MCP의 content 요약과 structuredContent로 분리합니다.
|
||||
*/
|
||||
@Service
|
||||
public class ToolExecutionResultFormatter {
|
||||
private static final int TEXT_PREVIEW_LIMIT = 2_000;
|
||||
|
||||
private final ObjectMapper json;
|
||||
private final SensitiveDataMasker masker;
|
||||
|
||||
public ToolExecutionResultFormatter(ObjectMapper json, SensitiveDataMasker masker) {
|
||||
this.json = json;
|
||||
this.masker = masker;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열을 content.text에 그대로 넣지 않고 structuredContent로 분리합니다.
|
||||
*/
|
||||
public ToolExecutionResult fromRawResponse(String toolName, String rawResponse) {
|
||||
long sizeBytes = rawResponse == null ? 0 : rawResponse.getBytes(StandardCharsets.UTF_8).length;
|
||||
if (rawResponse == null || rawResponse.isBlank()) {
|
||||
return result(ToolExecutionResult.ResultType.EMPTY, "Tool 응답이 비어 있습니다.",
|
||||
json.createObjectNode(), Map.of("toolName", toolName), false, false, sizeBytes);
|
||||
}
|
||||
try {
|
||||
JsonNode parsed = json.readTree(rawResponse);
|
||||
return fromJson(toolName, parsed, sizeBytes);
|
||||
} catch (Exception parseError) {
|
||||
ObjectNode metadata = json.createObjectNode();
|
||||
metadata.put("toolName", toolName);
|
||||
metadata.put("parseError", "JSON_PARSE_FAILED");
|
||||
metadata.put("preview", preview(rawResponse));
|
||||
return new ToolExecutionResult(
|
||||
ToolExecutionResult.ResultType.PLAIN_TEXT,
|
||||
List.of(new ToolExecutionResult.ContentItem("text", "Tool 응답이 JSON 형식이 아니어서 제한된 preview만 제공합니다.")),
|
||||
null,
|
||||
List.of(),
|
||||
Map.of("toolName", toolName, "parseError", "JSON_PARSE_FAILED", "preview", preview(rawResponse)),
|
||||
false,
|
||||
rawResponse.length() > TEXT_PREVIEW_LIMIT,
|
||||
sizeBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public ToolExecutionResult fromJson(String toolName, JsonNode parsed, long sizeBytes) {
|
||||
JsonNode masked = masker.mask(parsed);
|
||||
if (masked.isObject()) {
|
||||
ObjectNode object = (ObjectNode) masked;
|
||||
if (object.path("isError").asBoolean(false) || object.has("error") || object.has("failureType")) {
|
||||
return result(ToolExecutionResult.ResultType.ERROR, errorSummary(toolName, object), object,
|
||||
metadata(toolName, sizeBytes), true, object.path("truncated").asBoolean(false), sizeBytes);
|
||||
}
|
||||
JsonNode structured = object.has("structuredContent") ? object.path("structuredContent") : structuredFromObject(object);
|
||||
return result(ToolExecutionResult.ResultType.JSON_OBJECT, summary(toolName, object, structured), structured,
|
||||
metadata(toolName, sizeBytes), false, object.path("truncated").asBoolean(false), sizeBytes);
|
||||
}
|
||||
if (masked.isArray()) {
|
||||
ObjectNode structured = json.createObjectNode();
|
||||
structured.set("items", masked);
|
||||
structured.put("count", count(masked));
|
||||
return result(ToolExecutionResult.ResultType.JSON_ARRAY,
|
||||
toolName + " 결과 " + count(masked) + "건을 조회했습니다.",
|
||||
structured,
|
||||
metadata(toolName, sizeBytes),
|
||||
false,
|
||||
false,
|
||||
sizeBytes);
|
||||
}
|
||||
if (masked.isTextual()) {
|
||||
String text = masked.asText("");
|
||||
return result(ToolExecutionResult.ResultType.PLAIN_TEXT, preview(text), null,
|
||||
metadata(toolName, sizeBytes), false, text.length() > TEXT_PREVIEW_LIMIT, sizeBytes);
|
||||
}
|
||||
ObjectNode structured = json.createObjectNode();
|
||||
structured.set("value", masked);
|
||||
return result(ToolExecutionResult.ResultType.JSON_OBJECT, toolName + " 결과를 조회했습니다.",
|
||||
structured, metadata(toolName, sizeBytes), false, false, sizeBytes);
|
||||
}
|
||||
|
||||
private JsonNode structuredFromObject(ObjectNode object) {
|
||||
if (object.has("data")) {
|
||||
return object.path("data");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
private String summary(String toolName, ObjectNode object, JsonNode structured) {
|
||||
String message = object.path("message").asText("");
|
||||
if (!message.isBlank()) {
|
||||
return message;
|
||||
}
|
||||
if (structured != null && structured.isObject()) {
|
||||
long totalCount = structured.path("totalCount").asLong(-1);
|
||||
if (totalCount >= 0) {
|
||||
return toolName + " 대량 결과 preview 조회가 완료되었습니다. totalCount=" + totalCount;
|
||||
}
|
||||
}
|
||||
if (structured != null && structured.isArray()) {
|
||||
return toolName + " 결과 " + count(structured) + "건을 조회했습니다.";
|
||||
}
|
||||
return toolName + " 조회가 완료되었습니다.";
|
||||
}
|
||||
|
||||
private String errorSummary(String toolName, ObjectNode object) {
|
||||
String message = object.path("message").asText(object.path("error").asText(""));
|
||||
return message.isBlank() ? toolName + " 호출 중 오류가 발생했습니다." : message;
|
||||
}
|
||||
|
||||
private ToolExecutionResult result(ToolExecutionResult.ResultType type, String text, JsonNode structuredContent,
|
||||
Map<String, Object> metadata, boolean error, boolean truncated, long sizeBytes) {
|
||||
return new ToolExecutionResult(
|
||||
type,
|
||||
List.of(new ToolExecutionResult.ContentItem("text", text)),
|
||||
structuredContent,
|
||||
List.of(),
|
||||
metadata,
|
||||
error,
|
||||
truncated,
|
||||
sizeBytes);
|
||||
}
|
||||
|
||||
private Map<String, Object> metadata(String toolName, long sizeBytes) {
|
||||
return Map.of("toolName", toolName, "originalSizeBytes", sizeBytes);
|
||||
}
|
||||
|
||||
private int count(JsonNode node) {
|
||||
int count = 0;
|
||||
for (JsonNode ignored : node) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private String preview(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.length() <= TEXT_PREVIEW_LIMIT ? value : value.substring(0, TEXT_PREVIEW_LIMIT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.transport;
|
||||
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.FailureType;
|
||||
import io.shinhanlife.dap.biz.mcp.gateway.resilience.ToolExecutionException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpToolInvoker implements ToolInvoker {
|
||||
|
||||
private final RestClient restClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public HttpToolInvoker(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.restClient = RestClient.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode invoke(Map<String, Object> payload, String targetUrl) {
|
||||
try {
|
||||
Object httpResult = restClient.post()
|
||||
.uri(targetUrl)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
// TODO: Use actual tenant's key
|
||||
.header("X-Trace-Id", UUID.randomUUID().toString())
|
||||
.body(payload)
|
||||
.retrieve()
|
||||
.body(Object.class);
|
||||
|
||||
return extractData(objectMapper.valueToTree(httpResult));
|
||||
} catch (Exception e) {
|
||||
throw new ToolExecutionException(FailureType.SERVER_ERROR, "Tool Pod HTTP 호출 실패: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode extractData(JsonNode root) {
|
||||
if (!root.path("success").asBoolean(true)) {
|
||||
throw new ToolExecutionException(FailureType.BUSINESS_ERROR, "Tool 서버 업무 오류: " + root.path("error").asText());
|
||||
}
|
||||
return root.has("data") ? root.get("data") : root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.shinhanlife.dap.biz.mcp.gateway.transport;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tool 서버 호출 transport의 최소 공통 인터페이스입니다.
|
||||
*/
|
||||
public interface ToolInvoker {
|
||||
JsonNode invoke(Map<String, Object> payload, String targetUrl);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.converter;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSaveInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.converter
|
||||
* @className AccessMgmtConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public abstract class AccessMgmtConverter {
|
||||
|
||||
public abstract RoleListInDto toInDto(RoleListRequest request);
|
||||
|
||||
public abstract AthrSearchInDto toInDto(AthrSearchRequest request);
|
||||
|
||||
public abstract AthrSaveInDto toInDto(AthrSaveRequest request);
|
||||
|
||||
public abstract AthrSearchResponse toResponse(AthrOutDto outDto);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.domain.repository;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrItemOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleKnwlAthrInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleToolAthrInDto;
|
||||
import io.shinhanlife.glow.GlowMybatisMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.domain.repository
|
||||
* @className AccessMgmtMapper
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@GlowMybatisMapper
|
||||
public interface AccessMgmtMapper {
|
||||
|
||||
List<RoleListOutDto> selectRoles(RoleListInDto inDto);
|
||||
|
||||
List<AthrItemOutDto> selectAllTools();
|
||||
|
||||
List<AthrItemOutDto> selectAllKnwls();
|
||||
|
||||
List<String> selectGrantedToolIds(AthrSearchInDto inDto);
|
||||
|
||||
List<String> selectGrantedKnwlIds(AthrSearchInDto inDto);
|
||||
|
||||
void deleteToolAthr(@Param("systId") String systId, @Param("roleNo") String roleNo);
|
||||
|
||||
void insertToolAthr(RoleToolAthrInDto inDto);
|
||||
|
||||
void deleteKnwlAthr(@Param("systId") String systId, @Param("roleNo") String roleNo);
|
||||
|
||||
void insertKnwlAthr(RoleKnwlAthrInDto inDto);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.domain.service;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSaveInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.domain.service
|
||||
* @className AccessMgmtService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface AccessMgmtService {
|
||||
|
||||
List<RoleListOutDto> getRoles(RoleListInDto inDto);
|
||||
|
||||
AthrOutDto getAthr(AthrSearchInDto inDto);
|
||||
|
||||
void saveAthr(AthrSaveInDto inDto);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.domain.service.impl;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.domain.repository.AccessMgmtMapper;
|
||||
import io.shinhanlife.dap.biz.so.atm.domain.service.AccessMgmtService;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrItemOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSaveInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrSearchInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleKnwlAthrInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListInDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleToolAthrInDto;
|
||||
import io.shinhanlife.dap.common.util.SessionUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.domain.service.impl
|
||||
* @className AccessMgmtServiceImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AccessMgmtServiceImpl implements AccessMgmtService {
|
||||
|
||||
private final AccessMgmtMapper accessMgmtMapper;
|
||||
|
||||
private static final String SYSTEM_CD = "AXH";
|
||||
private static final String SYSTEM_PRG = "SOATM0100";
|
||||
|
||||
@Override
|
||||
public List<RoleListOutDto> getRoles(RoleListInDto inDto) {
|
||||
return accessMgmtMapper.selectRoles(inDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AthrOutDto getAthr(AthrSearchInDto inDto) {
|
||||
Set<String> grantedToolIds = accessMgmtMapper.selectGrantedToolIds(inDto)
|
||||
.stream().collect(Collectors.toSet());
|
||||
|
||||
Set<String> grantedKnwlIds = accessMgmtMapper.selectGrantedKnwlIds(inDto)
|
||||
.stream().collect(Collectors.toSet());
|
||||
|
||||
List<AthrItemOutDto> tools = accessMgmtMapper.selectAllTools()
|
||||
.stream()
|
||||
.peek(item -> item.setGranted(grantedToolIds.contains(item.getResourceId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<AthrItemOutDto> knwls = accessMgmtMapper.selectAllKnwls()
|
||||
.stream()
|
||||
.peek(item -> item.setGranted(grantedKnwlIds.contains(item.getResourceId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new AthrOutDto(tools, knwls);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void saveAthr(AthrSaveInDto inDto) {
|
||||
Date now = new Date();
|
||||
String systId = inDto.getSystId();
|
||||
String roleNo = inDto.getRoleNo();
|
||||
|
||||
accessMgmtMapper.deleteToolAthr(systId, roleNo);
|
||||
if (inDto.getGrantedToolIds() != null) {
|
||||
for (String toolId : inDto.getGrantedToolIds()) {
|
||||
accessMgmtMapper.insertToolAthr(buildToolAthrInDto(systId, roleNo, toolId, now));
|
||||
}
|
||||
}
|
||||
|
||||
accessMgmtMapper.deleteKnwlAthr(systId, roleNo);
|
||||
if (inDto.getGrantedKnwlIds() != null) {
|
||||
for (String knwlId : inDto.getGrantedKnwlIds()) {
|
||||
accessMgmtMapper.insertKnwlAthr(buildKnwlAthrInDto(systId, roleNo, knwlId, now));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RoleToolAthrInDto buildToolAthrInDto(String systId, String roleNo, String toolId, Date now) {
|
||||
RoleToolAthrInDto dto = new RoleToolAthrInDto();
|
||||
dto.setRoleToolAthrId("RTA-" + shortUuid());
|
||||
dto.setSystId(systId);
|
||||
dto.setRoleNo(roleNo);
|
||||
dto.setToolId(toolId);
|
||||
dto.setPuseYn("Y");
|
||||
fillAudit(dto, now);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private RoleKnwlAthrInDto buildKnwlAthrInDto(String systId, String roleNo, String knwlId, Date now) {
|
||||
RoleKnwlAthrInDto dto = new RoleKnwlAthrInDto();
|
||||
dto.setRoleKnwlAthrId("RKA-" + shortUuid());
|
||||
dto.setSystId(systId);
|
||||
dto.setRoleNo(roleNo);
|
||||
dto.setKnwlId(knwlId);
|
||||
dto.setPuseYn("Y");
|
||||
fillAudit(dto, now);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private void fillAudit(RoleToolAthrInDto dto, Date now) {
|
||||
String prafNo = SessionUtil.getPrafNo();
|
||||
String ognzNo = SessionUtil.getOgnzNo();
|
||||
dto.setSystRgiDt(now); dto.setSystRgiPrafNo(prafNo);
|
||||
dto.setSystRgiOgnzNo(ognzNo); dto.setSystRgiSystCd(SYSTEM_CD);
|
||||
dto.setSystRgiPrgrId(SYSTEM_PRG);
|
||||
dto.setSystChgDt(now); dto.setSystChgPrafNo(prafNo);
|
||||
dto.setSystChgOgnzNo(ognzNo); dto.setSystChgSystCd(SYSTEM_CD);
|
||||
dto.setSystChgPrgrId(SYSTEM_PRG);
|
||||
}
|
||||
|
||||
private void fillAudit(RoleKnwlAthrInDto dto, Date now) {
|
||||
String prafNo = SessionUtil.getPrafNo();
|
||||
String ognzNo = SessionUtil.getOgnzNo();
|
||||
dto.setSystRgiDt(now); dto.setSystRgiPrafNo(prafNo);
|
||||
dto.setSystRgiOgnzNo(ognzNo); dto.setSystRgiSystCd(SYSTEM_CD);
|
||||
dto.setSystRgiPrgrId(SYSTEM_PRG);
|
||||
dto.setSystChgDt(now); dto.setSystChgPrafNo(prafNo);
|
||||
dto.setSystChgOgnzNo(ognzNo); dto.setSystChgSystCd(SYSTEM_CD);
|
||||
dto.setSystChgPrgrId(SYSTEM_PRG);
|
||||
}
|
||||
|
||||
private String shortUuid() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 12).toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className AthrItemOutDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AthrItemOutDto {
|
||||
private String resourceId;
|
||||
private String resourceNm;
|
||||
private String resourceDs;
|
||||
private String typeCode;
|
||||
private boolean granted;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className AthrOutDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
public class AthrOutDto {
|
||||
private List<AthrItemOutDto> tools;
|
||||
private List<AthrItemOutDto> knwls;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className AthrSaveInDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AthrSaveInDto {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private List<String> grantedToolIds;
|
||||
private List<String> grantedKnwlIds;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className AthrSearchInDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AthrSearchInDto {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className RoleKnwlAthrInDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleKnwlAthrInDto {
|
||||
private String roleKnwlAthrId;
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private String knwlId;
|
||||
private String puseYn;
|
||||
|
||||
private Date systRgiDt;
|
||||
private String systRgiPrafNo;
|
||||
private String systRgiOgnzNo;
|
||||
private String systRgiSystCd;
|
||||
private String systRgiPrgrId;
|
||||
private Date systChgDt;
|
||||
private String systChgPrafNo;
|
||||
private String systChgOgnzNo;
|
||||
private String systChgSystCd;
|
||||
private String systChgPrgrId;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className RoleListInDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleListInDto {
|
||||
private String systId;
|
||||
private String puseYn;
|
||||
private String keyword;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className RoleListOutDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleListOutDto {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private String roleNm;
|
||||
private String roleDs;
|
||||
private String puseYn;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.dto
|
||||
* @className RoleToolAthrInDto
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleToolAthrInDto {
|
||||
private String roleToolAthrId;
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private String toolId;
|
||||
private String puseYn;
|
||||
|
||||
private Date systRgiDt;
|
||||
private String systRgiPrafNo;
|
||||
private String systRgiOgnzNo;
|
||||
private String systRgiSystCd;
|
||||
private String systRgiPrgrId;
|
||||
private Date systChgDt;
|
||||
private String systChgPrafNo;
|
||||
private String systChgOgnzNo;
|
||||
private String systChgSystCd;
|
||||
private String systChgPrgrId;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.presentation;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.usecase.AccessMgmtUseCase;
|
||||
import io.shinhanlife.glow.BaseResponse;
|
||||
import io.shinhanlife.glow.ResponseUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.presentation
|
||||
* @className SOATM0100Controller
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/so/atm")
|
||||
@RequiredArgsConstructor
|
||||
public class SOATM0100Controller {
|
||||
|
||||
private final AccessMgmtUseCase accessMgmtUseCase;
|
||||
|
||||
@PostMapping("/SOATM0100R")
|
||||
public ResponseEntity<BaseResponse<List<RoleListOutDto>>> getRoles(
|
||||
@RequestBody RoleListRequest request) {
|
||||
return ResponseUtil.ok(accessMgmtUseCase.getRoles(request));
|
||||
}
|
||||
|
||||
@PostMapping("/SOATM0101R")
|
||||
public ResponseEntity<BaseResponse<AthrSearchResponse>> getAthr(
|
||||
@RequestBody AthrSearchRequest request) {
|
||||
return ResponseUtil.ok(accessMgmtUseCase.getAthr(request));
|
||||
}
|
||||
|
||||
@PostMapping("/SOATM0100U")
|
||||
public ResponseEntity<BaseResponse<Void>> saveAthr(
|
||||
@RequestBody AthrSaveRequest request) {
|
||||
accessMgmtUseCase.saveAthr(request);
|
||||
return ResponseUtil.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.presentation.io;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
|
||||
* @className AthrSaveRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrSaveRequest {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
private List<String> grantedToolIds;
|
||||
private List<String> grantedKnwlIds;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.presentation.io;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
|
||||
* @className AthrSearchRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AthrSearchRequest {
|
||||
private String systId;
|
||||
private String roleNo;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.presentation.io;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.AthrItemOutDto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
|
||||
* @className AthrSearchResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AthrSearchResponse {
|
||||
private List<AthrItemOutDto> tools;
|
||||
private List<AthrItemOutDto> knwls;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.presentation.io;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.presentation.io
|
||||
* @className RoleListRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class RoleListRequest {
|
||||
private String systId;
|
||||
private String puseYn;
|
||||
private String keyword;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.usecase;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.usecase
|
||||
* @className AccessMgmtUseCase
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface AccessMgmtUseCase {
|
||||
|
||||
List<RoleListOutDto> getRoles(RoleListRequest request);
|
||||
|
||||
AthrSearchResponse getAthr(AthrSearchRequest request);
|
||||
|
||||
void saveAthr(AthrSaveRequest request);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.shinhanlife.dap.biz.so.atm.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.biz.so.atm.converter.AccessMgmtConverter;
|
||||
import io.shinhanlife.dap.biz.so.atm.domain.service.AccessMgmtService;
|
||||
import io.shinhanlife.dap.biz.so.atm.dto.RoleListOutDto;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSaveRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.AthrSearchResponse;
|
||||
import io.shinhanlife.dap.biz.so.atm.presentation.io.RoleListRequest;
|
||||
import io.shinhanlife.dap.biz.so.atm.usecase.AccessMgmtUseCase;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.biz.so.atm.usecase.impl
|
||||
* @className AccessMgmtUseCaseImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AccessMgmtUseCaseImpl implements AccessMgmtUseCase {
|
||||
|
||||
private final AccessMgmtService accessMgmtService;
|
||||
private final AccessMgmtConverter converter;
|
||||
|
||||
@Override
|
||||
public List<RoleListOutDto> getRoles(RoleListRequest request) {
|
||||
return accessMgmtService.getRoles(converter.toInDto(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AthrSearchResponse getAthr(AthrSearchRequest request) {
|
||||
return converter.toResponse(accessMgmtService.getAthr(converter.toInDto(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAthr(AthrSaveRequest request) {
|
||||
accessMgmtService.saveAthr(converter.toInDto(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.shinhanlife.dap.sample.converter;
|
||||
|
||||
import io.shinhanlife.dap.sample.domain.model.AppliSystNtfyPatiModel;
|
||||
import io.shinhanlife.dap.sample.dto.AppliSystNtfyRgiInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermResponse;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.converter
|
||||
* @className SampleConverter
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public abstract class SampleConverter {
|
||||
|
||||
public abstract StrnTermListInDTO convertRequestToDto(StrnTermRequest req);
|
||||
|
||||
public abstract StrnTermResponse convertDtoToResponse(StrnTermListOutDTO outDto);
|
||||
|
||||
public abstract AppliSystNtfyPatiModel convertDtoToModel(AppliSystNtfyRgiInDTO inDto);
|
||||
|
||||
public abstract AppliSystNtfyRgiInDTO convertAppliRequestToDto(AppliSystNtfyPatiRequest request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.shinhanlife.dap.sample.domain.model;
|
||||
|
||||
import io.shinhanlife.glow.db.dto.AuditInfo;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.domain.model
|
||||
* @className AppliSystNtfyPatiModel
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class AppliSystNtfyPatiModel extends AuditInfo {
|
||||
private String appliSystNtfyPatiId;
|
||||
private String appliSystNtfyKdCd;
|
||||
private String appliDutjCd;
|
||||
private String appliDutjPrjcCd;
|
||||
private String ntfyMsgCt;
|
||||
private String ntfyOccDt;
|
||||
private String ntfyCfmDt;
|
||||
private String ntfyTrgtPrafNo;
|
||||
private String shmsPmlYn;
|
||||
private String ntfyCfmYn;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.shinhanlife.dap.sample.domain.repository;
|
||||
|
||||
import io.shinhanlife.dap.sample.domain.model.AppliSystNtfyPatiModel;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.glow.GlowIndexPaging;
|
||||
import io.shinhanlife.glow.GlowMybatisMapper;
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.domain.repository
|
||||
* @className GlowSampleRepository
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@GlowMybatisMapper
|
||||
public interface GlowSampleRepository {
|
||||
|
||||
@GlowIndexPaging
|
||||
List<StrnTermListOutDTO.StrnTerm> selectStrnTerm(StrnTermListInDTO inDto, PageInfo pageInfo);
|
||||
|
||||
int insertAppliSystNtfyPati(AppliSystNtfyPatiModel appliSystNtfyPatiModel);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.shinhanlife.dap.sample.domain.service;
|
||||
|
||||
import io.shinhanlife.dap.sample.dto.AppliSystNtfyRgiInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.domain.service
|
||||
* @className GlowSampleService
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface GlowSampleService {
|
||||
StrnTermListOutDTO getStrnTerms(StrnTermListInDTO inDto, PageInfo pageInfo);
|
||||
int insertAppliSystNtfyPati(AppliSystNtfyRgiInDTO inDto);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.shinhanlife.dap.sample.domain.service.impl;
|
||||
|
||||
import io.shinhanlife.dap.sample.converter.SampleConverter;
|
||||
import io.shinhanlife.dap.sample.domain.model.AppliSystNtfyPatiModel;
|
||||
import io.shinhanlife.dap.sample.domain.repository.GlowSampleRepository;
|
||||
import io.shinhanlife.dap.sample.domain.service.GlowSampleService;
|
||||
import io.shinhanlife.dap.sample.dto.AppliSystNtfyRgiInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListInDTO;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.glow.GlowLogger;
|
||||
import io.shinhanlife.glow.GlowLogTarget;
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.domain.service.impl
|
||||
* @className GlowSampleServiceImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GlowSampleServiceImpl implements GlowSampleService {
|
||||
|
||||
@GlowLogTarget(GlowLogTarget.Target.FILE)
|
||||
private final GlowLogger log;
|
||||
private final GlowSampleRepository glowSampleRepository;
|
||||
private final SampleConverter sampleConverter;
|
||||
|
||||
@Override
|
||||
public StrnTermListOutDTO getStrnTerms(StrnTermListInDTO inDto, PageInfo pageInfo) {
|
||||
List<StrnTermListOutDTO.StrnTerm> out = glowSampleRepository.selectStrnTerm(inDto, pageInfo);
|
||||
return StrnTermListOutDTO.builder()
|
||||
.strnTerms(out)
|
||||
.pageInfo(pageInfo)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertAppliSystNtfyPati(AppliSystNtfyRgiInDTO inDto) {
|
||||
AppliSystNtfyPatiModel appliSystNtfyPatiModel = sampleConverter.convertDtoToModel(inDto);
|
||||
return glowSampleRepository.insertAppliSystNtfyPati(appliSystNtfyPatiModel);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.shinhanlife.dap.sample.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.dto
|
||||
* @className AppliSystNtfyRgiInDTO
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AppliSystNtfyRgiInDTO {
|
||||
private String appliSystNtfyPatiId;
|
||||
private String appliSystNtfyKdCd;
|
||||
private String appliDutjCd;
|
||||
private String appliDutjPrjcCd;
|
||||
private String ntfyMsgCt;
|
||||
private String ntfyOccDt;
|
||||
private String ntfyCfmDt;
|
||||
private String ntfyTrgtPrafNo;
|
||||
private String shmsPmlYn;
|
||||
private String ntfyCfmYn;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.shinhanlife.dap.sample.dto;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.dto
|
||||
* @className StrnTermListInDTO
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class StrnTermListInDTO {
|
||||
private String strnTermHanNm;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.shinhanlife.dap.sample.dto;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.dto
|
||||
* @className StrnTermListOutDTO
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class StrnTermListOutDTO {
|
||||
private List<StrnTerm> strnTerms;
|
||||
private PageInfo pageInfo;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
public static class StrnTerm{
|
||||
private String strnTermHanNm;
|
||||
private String strnTermEngcAbrNm;
|
||||
private String strnTermEngNm;
|
||||
private String strnTermScrnTermNm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.shinhanlife.dap.sample.presentation;
|
||||
|
||||
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiResponse;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermResponse;
|
||||
import io.shinhanlife.dap.sample.usecase.GlowSampleUseCase;
|
||||
import io.shinhanlife.glow.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.presentation
|
||||
* @className GlowSampleController
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
|
||||
public class GlowSampleController {
|
||||
@GlowLogTarget({GlowLogTarget.Target.CONSOLE, GlowLogTarget.Target.FILE})
|
||||
private final GlowLogger log;
|
||||
private final GlowSampleUseCase glowSampleUseCase;
|
||||
|
||||
@GlowControllerId(value = "selectStrnTerm")
|
||||
@PostMapping("/selectStrnTerm")
|
||||
public ResponseEntity<BaseResponse<StrnTermResponse>> getStrnTerms(@RequestBody StrnTermRequest request) {
|
||||
return ResponseUtil.ok(glowSampleUseCase.getStrnTerms(request));
|
||||
}
|
||||
|
||||
|
||||
@GlowControllerId(value = "insertAp")
|
||||
@PostMapping("/insertAppliSystNtfyPati")
|
||||
public ResponseEntity<BaseResponse<AppliSystNtfyPatiResponse>> insertAppliSystNtfyPati(@RequestBody AppliSystNtfyPatiRequest request) {
|
||||
return ResponseUtil.ok(glowSampleUseCase.insertAppliSystNtfyPati(request));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.shinhanlife.dap.sample.presentation.io;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.presentation.io
|
||||
* @className AppliSystNtfyPatiRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class AppliSystNtfyPatiRequest {
|
||||
private String appliSystNtfyPatiId;
|
||||
private String appliSystNtfyKdCd;
|
||||
private String appliDutjCd;
|
||||
private String appliDutjPrjcCd;
|
||||
private String ntfyMsgCt;
|
||||
private String ntfyOccDt;
|
||||
private String ntfyCfmDt;
|
||||
private String ntfyTrgtPrafNo;
|
||||
private String shmsPmlYn;
|
||||
private String ntfyCfmYn;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.shinhanlife.dap.sample.presentation.io;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.presentation.io
|
||||
* @className AppliSystNtfyPatiResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class AppliSystNtfyPatiResponse {
|
||||
private String successYn;
|
||||
private String msg;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.shinhanlife.dap.sample.presentation.io;
|
||||
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.presentation.io
|
||||
* @className StrnTermRequest
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class StrnTermRequest {
|
||||
private PageInfo pageInfo;
|
||||
|
||||
private String strnTermHanNm;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.shinhanlife.dap.sample.presentation.io;
|
||||
|
||||
import io.shinhanlife.glow.PageInfo;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.presentation.io
|
||||
* @className StrnTermResponse
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
public class StrnTermResponse {
|
||||
|
||||
private List<StrnTerm> strnTerms;
|
||||
private PageInfo pageInfo;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class StrnTerm{
|
||||
private String strnTermHanNm;
|
||||
private String strnTermEngcAbrNm;
|
||||
private String strnTermEngNm;
|
||||
private String strnTermScrnTermNm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.shinhanlife.dap.sample.usecase;
|
||||
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiResponse;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermResponse;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.usecase
|
||||
* @className GlowSampleUseCase
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public interface GlowSampleUseCase {
|
||||
StrnTermResponse getStrnTerms(StrnTermRequest req);
|
||||
AppliSystNtfyPatiResponse insertAppliSystNtfyPati(AppliSystNtfyPatiRequest req);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.shinhanlife.dap.sample.usecase.impl;
|
||||
|
||||
import io.shinhanlife.dap.sample.converter.SampleConverter;
|
||||
import io.shinhanlife.dap.sample.domain.service.GlowSampleService;
|
||||
import io.shinhanlife.dap.sample.dto.StrnTermListOutDTO;
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.AppliSystNtfyPatiResponse;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermRequest;
|
||||
import io.shinhanlife.dap.sample.presentation.io.StrnTermResponse;
|
||||
import io.shinhanlife.dap.sample.usecase.GlowSampleUseCase;
|
||||
import io.shinhanlife.glow.GlowAppServiceId;
|
||||
import io.shinhanlife.glow.GlowLogTarget;
|
||||
import io.shinhanlife.glow.GlowLogger;
|
||||
import io.shinhanlife.glow.GlowServiceGroupId;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @package io.shinhanlife.dap.sample.usecase.impl
|
||||
* @className GlowSampleUseCaseImpl
|
||||
* @description AX HUB 시스템 처리 클래스
|
||||
* @author 김형식
|
||||
* @create 2026.09.01
|
||||
* <pre>
|
||||
* ---------- 개정이력 ----------
|
||||
* 수정일 수정자 수정내용
|
||||
* ---------- -------- ---------------------------
|
||||
* 2026.09.01 김형식 최초생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@GlowServiceGroupId(value = "GlowSample", description="GlowSampleUseCase")
|
||||
public class GlowSampleUseCaseImpl implements GlowSampleUseCase {
|
||||
@GlowLogTarget(GlowLogTarget.Target.FILE)
|
||||
private final GlowLogger log;
|
||||
private final GlowSampleService glowSampleService;
|
||||
private final SampleConverter converter;
|
||||
|
||||
@GlowAppServiceId(value = "SAMPLE-DB-0001", description = "표준용어조회(DB)")
|
||||
public StrnTermResponse getStrnTerms(StrnTermRequest req) {
|
||||
StrnTermListOutDTO outDto = glowSampleService.getStrnTerms(converter.convertRequestToDto(req), req.getPageInfo());
|
||||
|
||||
return converter.convertDtoToResponse(outDto);
|
||||
}
|
||||
|
||||
@GlowAppServiceId(value = "SAMPLE-DB-0002", description = "어플리케이션시스템알림내역등록")
|
||||
public AppliSystNtfyPatiResponse insertAppliSystNtfyPati(AppliSystNtfyPatiRequest req) {
|
||||
int rtn = glowSampleService.insertAppliSystNtfyPati(converter.convertAppliRequestToDto(req));
|
||||
|
||||
return AppliSystNtfyPatiResponse.builder()
|
||||
.successYn(rtn > 0 ? "Y" : "N")
|
||||
.msg(rtn > 0 ? "성공적으로 처리 되었습니다." : "처리중 오류발생!!")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
160
dap-gateway/src/main/resources/SOATM0100_DDL.sql
Normal file
160
dap-gateway/src/main/resources/SOATM0100_DDL.sql
Normal file
@@ -0,0 +1,160 @@
|
||||
-- ============================================================
|
||||
-- AX HUB 역할-Tool/지식 권한 관리 DDL
|
||||
-- 대상 스키마 : 프로젝트 AP 스키마 (S_EIAM_TIS 는 읽기전용 참조)
|
||||
-- 작성일 : 2026-06-25
|
||||
-- ============================================================
|
||||
|
||||
-- ① Tool 마스터
|
||||
CREATE TABLE AX_TOOL_MST (
|
||||
TOOL_ID VARCHAR(20) NOT NULL, -- Tool ID (PK)
|
||||
TOOL_NM VARCHAR(100) NOT NULL, -- Tool명
|
||||
TOOL_DS VARCHAR(500), -- Tool설명
|
||||
TOOL_TYPE_CD VARCHAR(20), -- Tool유형코드 (MCP / FUNC / EXT)
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',-- 사용여부
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_TOOL_MST PRIMARY KEY (TOOL_ID)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE AX_TOOL_MST IS 'AX HUB Tool 마스터';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_ID IS 'Tool ID (PK)';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_NM IS 'Tool명';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_DS IS 'Tool설명';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_TYPE_CD IS 'Tool유형코드 (MCP/FUNC/EXT)';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.PUSE_YN IS '사용여부 (Y/N)';
|
||||
|
||||
|
||||
-- ② 지식(Knowledge) 마스터
|
||||
CREATE TABLE AX_KNWL_MST (
|
||||
KNWL_ID VARCHAR(20) NOT NULL, -- 지식 ID (PK)
|
||||
KNWL_NM VARCHAR(100) NOT NULL, -- 지식명
|
||||
KNWL_DS VARCHAR(500), -- 지식설명
|
||||
KNWL_TYPE_CD VARCHAR(20), -- 지식유형코드 (PRODUCT/MANUAL/LEGAL 등)
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_KNWL_MST PRIMARY KEY (KNWL_ID)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE AX_KNWL_MST IS 'AX HUB 지식 마스터';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_ID IS '지식 ID (PK)';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_NM IS '지식명';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_DS IS '지식설명';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_TYPE_CD IS '지식유형코드 (PRODUCT/MANUAL/LEGAL/REPORT)';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.PUSE_YN IS '사용여부 (Y/N)';
|
||||
|
||||
|
||||
-- ③ 역할-Tool 권한 매핑
|
||||
-- SYST_ID + ROLE_NO → S_EIAM_TIS.AA_ROLE 참조 (FK 미설정: 스키마 분리 환경)
|
||||
CREATE TABLE AX_ROLE_TOOL_ATHR (
|
||||
ROLE_TOOL_ATHR_ID VARCHAR(20) NOT NULL, -- 역할Tool권한ID (PK)
|
||||
SYST_ID VARCHAR(10) NOT NULL, -- 시스템ID (EIAM 연동 키)
|
||||
ROLE_NO VARCHAR(50) NOT NULL, -- 역할번호 (EIAM AA_ROLE.ROLE_NO)
|
||||
TOOL_ID VARCHAR(20) NOT NULL, -- Tool ID
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_ROLE_TOOL_ATHR PRIMARY KEY (ROLE_TOOL_ATHR_ID)
|
||||
);
|
||||
|
||||
-- (SYST_ID, ROLE_NO, TOOL_ID) 중복 방지
|
||||
CREATE UNIQUE INDEX UK_AX_ROLE_TOOL_ATHR
|
||||
ON AX_ROLE_TOOL_ATHR (SYST_ID, ROLE_NO, TOOL_ID);
|
||||
|
||||
COMMENT ON TABLE AX_ROLE_TOOL_ATHR IS '역할-Tool 권한 매핑';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.ROLE_TOOL_ATHR_ID IS '역할Tool권한ID (PK)';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.ROLE_NO IS '역할번호 (EIAM)';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.TOOL_ID IS 'Tool ID';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.PUSE_YN IS '사용여부';
|
||||
|
||||
|
||||
-- ④ 역할-지식 권한 매핑
|
||||
CREATE TABLE AX_ROLE_KNWL_ATHR (
|
||||
ROLE_KNWL_ATHR_ID VARCHAR(20) NOT NULL, -- 역할지식권한ID (PK)
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
ROLE_NO VARCHAR(50) NOT NULL,
|
||||
KNWL_ID VARCHAR(20) NOT NULL,
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_ROLE_KNWL_ATHR PRIMARY KEY (ROLE_KNWL_ATHR_ID)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX UK_AX_ROLE_KNWL_ATHR
|
||||
ON AX_ROLE_KNWL_ATHR (SYST_ID, ROLE_NO, KNWL_ID);
|
||||
|
||||
COMMENT ON TABLE AX_ROLE_KNWL_ATHR IS '역할-지식 권한 매핑';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.ROLE_KNWL_ATHR_ID IS '역할지식권한ID (PK)';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.ROLE_NO IS '역할번호 (EIAM)';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.KNWL_ID IS '지식ID';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.PUSE_YN IS '사용여부';
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 샘플 데이터 (개발용)
|
||||
-- ============================================================
|
||||
INSERT INTO AX_TOOL_MST
|
||||
(TOOL_ID, TOOL_NM, TOOL_DS, TOOL_TYPE_CD, PUSE_YN,
|
||||
SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID)
|
||||
VALUES
|
||||
('TOOL_A', '지식 검색', '지식베이스 내 문서 의미 기반 검색', 'MCP', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('TOOL_B', 'DB 조회', '사내 데이터베이스 직접 조회 (GLOW)', 'MCP', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('TOOL_C', '문서 생성', 'AI 기반 문서 초안 자동 생성', 'FUNC', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100');
|
||||
|
||||
INSERT INTO AX_KNWL_MST
|
||||
(KNWL_ID, KNWL_NM, KNWL_DS, KNWL_TYPE_CD, PUSE_YN,
|
||||
SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID)
|
||||
VALUES
|
||||
('KNWL_Q', '상품기초서류', '보험 상품 기초서류 약 8만건', 'PRODUCT', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('KNWL_W', '업무매뉴얼', '내부 업무 매뉴얼 및 프로세스 가이드', 'MANUAL', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('KNWL_E', '법률/규정', '보험업법 및 금융당국 규정', 'LEGAL', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100');
|
||||
|
||||
COMMIT;
|
||||
19
dap-gateway/src/main/resources/application-dev.properties
Normal file
19
dap-gateway/src/main/resources/application-dev.properties
Normal file
@@ -0,0 +1,19 @@
|
||||
# Dev 환경 전용 설정 (개발 서버 DB 연결 정보 등)
|
||||
# spring.datasource.url=jdbc:oracle:thin:@//dev-db-host:1521/XEPDB1
|
||||
# spring.datasource.driverClassName=oracle.jdbc.OracleDriver
|
||||
# spring.datasource.username=dev_user
|
||||
# spring.datasource.password=dev_password
|
||||
|
||||
# Render 클라우드 환경 전용 Fallback 라우팅
|
||||
mcp.gateway.fallback.routes.sms=https://dap-tool-sms.onrender.com
|
||||
mcp.gateway.fallback.routes.email=https://dap-tool-email.onrender.com
|
||||
mcp.gateway.fallback.default-url=https://dap-tool-other.onrender.com
|
||||
mcp.gateway.fallback.routes.payment=https://dap-tool-payment.onrender.com
|
||||
mcp.gateway.fallback.routes.hr=https://dap-tool-hr.onrender.com
|
||||
|
||||
# --- 신한라이프 EAI/MCI 연계 IP 정보 (개발 환경) ---
|
||||
shinhan.integration.envrTypeCd=D
|
||||
shinhan.integration.eai.url=http://10.176.32.181
|
||||
shinhan.integration.internalMci.url=http://10.176.32.173
|
||||
shinhan.integration.bancaMci.url=http://10.176.32.117
|
||||
shinhan.integration.externalMci.url=http://10.176.32.176
|
||||
27
dap-gateway/src/main/resources/application-local.properties
Normal file
27
dap-gateway/src/main/resources/application-local.properties
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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
|
||||
mcp.adapter.url=http://localhost:${server.port}/rpc/v1/execute
|
||||
mcp.gateway.url=http://localhost:${server.port}/mcp/api/v1
|
||||
mcp.tool.host=localhost
|
||||
|
||||
# Disable Kafka
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
||||
|
||||
# Suppress Kafka Connection Logs
|
||||
logging.level.org.apache.kafka=ERROR
|
||||
12
dap-gateway/src/main/resources/application-prod.properties
Normal file
12
dap-gateway/src/main/resources/application-prod.properties
Normal file
@@ -0,0 +1,12 @@
|
||||
# Prod 환경 전용 설정 (운영 서버 DB 연결 정보 등)
|
||||
# spring.datasource.url=jdbc:oracle:thin:@//prod-db-host:1521/PROD
|
||||
# spring.datasource.driverClassName=oracle.jdbc.OracleDriver
|
||||
# spring.datasource.username=prod_user
|
||||
# spring.datasource.password=prod_password
|
||||
|
||||
# --- 신한라이프 EAI/MCI 연계 IP 정보 (운영 환경) ---
|
||||
shinhan.integration.envrTypeCd=R
|
||||
shinhan.integration.eai.url=http://10.172.32.181
|
||||
shinhan.integration.internalMci.url=http://10.172.32.173
|
||||
shinhan.integration.bancaMci.url=http://10.172.32.117
|
||||
shinhan.integration.externalMci.url=http://10.172.32.177
|
||||
51
dap-gateway/src/main/resources/application.properties
Normal file
51
dap-gateway/src/main/resources/application.properties
Normal file
@@ -0,0 +1,51 @@
|
||||
spring.application.name=dap-admin
|
||||
|
||||
# Gateway Fallback Routing (For unregistered tools)
|
||||
mcp.gateway.fallback.routes.sms=http://tool-sms:8082
|
||||
mcp.gateway.fallback.routes.email=http://tool-email:8083
|
||||
mcp.gateway.fallback.default-url=http://tool-other:8084
|
||||
mcp.gateway.fallback.routes.payment=http://tool-payment:8085
|
||||
mcp.gateway.fallback.routes.hr=http://tool-hr:8086
|
||||
|
||||
|
||||
|
||||
server.port=8081
|
||||
server.http2.enabled=true
|
||||
|
||||
# 기본적으로 local 환경을 실행
|
||||
spring.profiles.active=local
|
||||
|
||||
# MyBatis 공통 설정
|
||||
mybatis.mapper-locations=classpath:mapper/**/*.xml
|
||||
mybatis.type-aliases-package=
|
||||
mybatis.configuration.map-underscore-to-camel-case=true
|
||||
spring.sql.init.mode=always
|
||||
|
||||
logging.level.com.corundumstudio.socketio=DEBUG
|
||||
|
||||
# 우아한 종료 (Graceful Shutdown) 설정
|
||||
server.shutdown=graceful
|
||||
spring.lifecycle.timeout-per-shutdown-phase=20s
|
||||
# Suppress Kafka Connection Logs
|
||||
logging.level.org.apache.kafka=ERROR
|
||||
|
||||
# Spring AI MCP Server Settings
|
||||
# spring.ai.mcp.server.protocol=STATELESS
|
||||
mcp.agent-claims-required=false
|
||||
mcp.trusted-claims-required=false
|
||||
mcp.write-approval-required=false
|
||||
mcp.default-page-size=100
|
||||
mcp.max-page-size=500
|
||||
mcp.max-pages-per-call=3
|
||||
mcp.max-stream-bytes=1048576
|
||||
mcp.max-response-bytes-from-tool=5242880
|
||||
mcp.tool-registry-sync-interval-millis=5000
|
||||
|
||||
# API 보안 키 설정
|
||||
mcp.security.tenant-domains.TESTER-DEV=ALL
|
||||
# Spring AI OpenAI / Gemini API Config
|
||||
spring.ai.openai.api-key=AQ.Ab8RN6KFZggsQf8iooY1v_3h3vp2TIjiYB54dV4Yay3vVKEMtg
|
||||
spring.ai.openai.base-url=https://generativelanguage.googleapis.com/v1beta/openai/
|
||||
spring.ai.openai.chat.options.model=gemini-flash-latest
|
||||
spring.ai.openai.chat.options.temperature=0.3
|
||||
|
||||
258
dap-gateway/src/main/resources/data.sql
Normal file
258
dap-gateway/src/main/resources/data.sql
Normal file
@@ -0,0 +1,258 @@
|
||||
SET SCHEMA S_TIS;
|
||||
|
||||
-- INSERT 문 재정렬: 부모부터 삽입 (루트 -> 자식 순)
|
||||
-- 루트 메뉴 먼저 (11: 샘플 화면, 10: AX HUB)
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (11, NULL, 'Sample', '/sample', '샘플 화면', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (10, NULL, 'AxHub', '/axhub', 'AX HUB', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
-- 샘플 화면(11번) 자식 메뉴
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (1, 11, 'Dashboard', '/dashboard', '대시보드', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (5, 11, 'Widgets', '/widgets', '컴포넌트 모음', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (126, 11, 'Template', '/template', '템플릿 센터', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (4, 11, 'Article', '/article', '기사 관리', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 3, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (2, 11, 'User', '/user', '사용자 관리', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 4, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (3, 11, 'Menu', '/menu', '메뉴 관리', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 5, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (18, 11, 'Result', '/result', '결과 페이지', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 6, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (8, 11, 'Exception', '/exception', '예외 페이지', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 7, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (9, 11, 'System', '/system', '시스템 설정', '', 'N', NULL, 'N', 'N', NULL, 'N', 'N', 8, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 1번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (101, 1, NULL, '/dashboard/console', '콘솔', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (102, 1, NULL, '/dashboard/analysis', '분석 페이지', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (103, 1, NULL, '/dashboard/kanban', '칸반보드', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 5번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (503, 5, NULL, '/widgets/icon-list', '아이콘 목록', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (504, 5, NULL, '/widgets/icon-selector', '아이콘 선택기', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (506, 5, NULL, '/widgets/excel', 'Excel 가져오기 내보내기', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (508, 5, NULL, '/widgets/count-to', '숫자 롤링', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'N', 3, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (509, 5, NULL, '/widgets/toastUI', 'ToastUI 에디터', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 4, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (511, 5, NULL, '/widgets/context-menu', '오른쪽 클릭 메뉴', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 5, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (513, 5, NULL, '/widgets/drag', '드래그', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 6, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 126번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (12601, 126, NULL, '/template/chat', '채팅', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (12602, 126, NULL, '/template/cards', '카드', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'N', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (12603, 126, NULL, '/template/banners', '배너', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'N', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (12604, 126, NULL, '/template/charts', '차트', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'N', 3, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (12605, 126, NULL, '/template/calendar', '캘린더', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 4, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 4번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (202, 4, NULL, '/article/article-list', '기사 목록', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (204, 4, NULL, '/article/detail', '기사 상세', NULL, 'N', NULL, 'Y', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (205, 4, NULL, '/article/comment', '댓글 관리', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (201, 4, NULL, '/article/article-publish', '기사 게시', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 3, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 2번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (301, 2, NULL, '/user/account', '계정 관리', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (302, 2, NULL, '/user/department', '부서 관리', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (303, 2, NULL, '/user/role', '역할 권한', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (304, 2, NULL, '/user/user', '마이페이지', NULL, 'N', NULL, 'Y', 'Y', NULL, 'N', 'Y', 3, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 3번 자식 (중첩 메뉴 부모 ID 402로 수정)
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (401, 3, NULL, '/menu/menu', '메뉴 권한', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (411, 3, NULL, '/menu/permission', '권한 제어', '', 'N', 'new', 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (402, 3, NULL, '/menu/nested', '중첩 메뉴', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 402번 자식 (중첩 메뉴)
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (40201, 402, NULL, '/menu/nested/menu1', '메뉴1', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (40202, 402, NULL, '/menu/nested/menu2', '메뉴2', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (40203, 402, NULL, '/menu/nested/menu3', '메뉴3', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 40202 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (4020201, 40202, NULL, '/menu/nested/menu2/menu2-1', '메뉴2-1', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 40203 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (4020301, 40203, NULL, '/menu/nested/menu3/menu3-1', '메뉴3-1', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (4020302, 40203, NULL, '/menu/nested/menu3/menu3-2', '메뉴3-2', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 4020302 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (402030201, 4020302, NULL, '/menu/nested/menu3/menu3-2/menu3-2-1', '메뉴3-2-1', '', 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 18번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (405, 18, NULL, '/result/success', '성공 페이지', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (404, 18, NULL, '/result/fail', '실패 페이지', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 8번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (801, 8, NULL, '/exception/403', '403', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (802, 8, NULL, '/exception/404', '404', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (803, 8, NULL, '/exception/500', '500', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 9번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (901, 9, NULL, '/system/setting', '시스템 설정', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (902, 9, NULL, '/system/api', 'API 관리', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (903, 9, NULL, '/system/log', '시스템 로그', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 10번 자식
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (1001, 10, NULL, '/axhub/auth', '권한설정', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 0, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO ZT_MENU (MENU_ID, PARENT_MENU_ID, NAME, PATH, TITLE, ICON, SHOW_BADGE, SHOW_TEXT_BADGE, IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE, ORDER_SEQ, IS_ACTIVE, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES (1002, 10, 'MenuMgmt', '/axhub/menu-mgmt', '메뉴 관리', NULL, 'N', NULL, 'N', 'N', NULL, 'N', 'Y', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- 샘플 데이터 (개발용)
|
||||
-- ============================================================
|
||||
INSERT INTO AX_TOOL_MST
|
||||
(TOOL_ID, TOOL_NM, TOOL_DS, TOOL_TYPE_CD, PUSE_YN,
|
||||
SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID)
|
||||
VALUES
|
||||
('TOOL_A', '지식 검색', '지식베이스 내 문서 의미 기반 검색', 'MCP', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('TOOL_B', 'DB 조회', '사내 데이터베이스 직접 조회 (GLOW)', 'MCP', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('TOOL_C', '문서 생성', 'AI 기반 문서 초안 자동 생성', 'FUNC', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100');
|
||||
|
||||
INSERT INTO AX_KNWL_MST
|
||||
(KNWL_ID, KNWL_NM, KNWL_DS, KNWL_TYPE_CD, PUSE_YN,
|
||||
SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID)
|
||||
VALUES
|
||||
('KNWL_Q', '상품기초서류', '보험 상품 기초서류 약 8만건', 'PRODUCT', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('KNWL_W', '업무매뉴얼', '내부 업무 매뉴얼 및 프로세스 가이드', 'MANUAL', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100'),
|
||||
('KNWL_E', '법률/규정', '보험업법 및 금융당국 규정', 'LEGAL', 'Y',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100',
|
||||
CURRENT_DATE, 'SYSTEM', 'SYSTEM', 'AXH', 'SOATM0100');
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 역할 데이터
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','110','AI아키텍트','AI 시스템 아키텍처 설계 및 에이전트 구조 검토 담당자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','106','데이터분석사용자','AI 분석 대시보드 및 에이전트 통계 조회 가능 사용자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','29','일반사용자','AI Hub 기본 이용 권한이 부여된 전사 일반 직원','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','126','AI개발책임자','AI 서비스 개발을 총괄하는 책임자로 에이전트 빌드 파이프라인 최종 승인 권한 보유','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','133','AI운영책임자','AI 서비스 운영 및 에이전트 인프라를 총괄하는 책임자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','122','AI위험관리자','AI 모델 리스크 평가 및 컴플라이언스 모니터링 담당자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','125','AI감사인','AI 에이전트 활동 이력 및 데이터 사용 내역 감사 담당자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','69','AI배포검토관','AI 모델 및 에이전트 서비스 배포 전 변경 검토 회의 주관자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','4','AI개발자','AI 에이전트 및 서비스 개발에 참여하는 내부 및 협력사 직원','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','18','시스템관리자','AI Hub 전체 시스템 설정 및 에이전트 플랫폼 관리 담당자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','123','AI서비스요청자','AI 기능 개발 및 에이전트 도입을 요청하는 전사 직원','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','132','AI검증자','AI 모델 및 에이전트 기능 검증에 참여하는 전사 직원','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','127','현업AI위험관리자','현업 부서 내 AI 서비스 도입 리스크 관리를 수행하는 직원','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','128','현업AI책임자','현업 부서 내 AI 서비스 개발 및 도입을 책임지는 직원','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','142','AI개발부서장','AI 개발 조직의 부서장으로 에이전트 개발 방향성 결정 권한 보유','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','143','AI운영부서장','AI 운영 조직의 부서장으로 에이전트 서비스 안정성 총괄','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','225','지식감사인','AI 지식베이스 무결성 및 에이전트 활동 이력 독립 감사 담당자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-05-06 15:45:08','61003745','0997100','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO,ROLE_NM,ROLE_DS,ASST_OWNR_ROLE_TYPE_CD,MASK_ECPT_MD_CD,INDV_INFO_ATHR_YN,MAIN_CST_IFIN_YN,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','144','AI품질관리자','AI 모델 성능 및 응답 품질 제3자 검증 담당자','ZZZ','N','N','N','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-05-03 13:30:07','61003745','0997100','SAA','EIAM' ) ;
|
||||
|
||||
-- 사용자 그룹
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011296', NULL ,'어플리케이션아키텍트','어플리케이션아키텍트','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011297', NULL ,'통계사용자','통계화면을 볼 수 있는 사용자','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011298', NULL ,'IT내부직원','ICT본부내 신한라이프 정직원 자동부여','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011299', NULL ,'IT개발책임자','(ICT본부만 해당)IT개발부서장 및 IT개발부서장 권한이양받은 파트장 (근거 있어야 함)','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011300', NULL ,'IT운영책임자','(ICT본부만 해당)IT운영부서장 및 IT운영부서장 권한이양받은 파트장 (근거 있어야 함)','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011301', NULL ,'RM','ICT본부내 IT RM그룹','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011302', NULL ,'IT감사인','ICT본부내 IT감사인','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011303', NULL ,'이행검토주관자','ICT본부내 변경관리회의 주관자','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011304', NULL ,'IT개발자','ICT본부내 신한라이프 정직원-도급직원 자동부여','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011305', NULL ,'관리자','IT개발관리시스템 설정 및 시스템 관리','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011306', NULL ,'IT개발의뢰자','신한라이프 전사 정직원 자동부여','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011307', NULL ,'UAT테스터','신한라이프 전사 정직원 자동부여','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011308', NULL ,'현업_RM','(ICT본부외 해당)IT개발업무를 수행하는 현업부서내 RM역할 수행직원','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011309', NULL ,'현업_개발책임자','(ICT본부외 해당)IT개발업무를 수행하는 현업부서내 IT개발부서장역할을 수행하는 직원','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011310', NULL ,'IT개발부서장','ICT본부내 개발 부서장 자동부여','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011311', NULL ,'IT운영부서장','ICT본부내 운영 부서장 자동부여','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011312', NULL ,'감사인','감사팀내 IT감사인','N',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-05-10 16:30:32','61003745','0997100','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID,OGNZ_NO,USER_GROU_NM,USER_GROU_DS,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000000205','UG_0011313', NULL ,'품질관리자','제3자점검자','Y',TIMESTAMP '2022-04-19 12:19:18','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:17','99999999','9999999','SAA','EIAM' ) ;
|
||||
|
||||
-- 사용자 그룹-역할 관계
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000030452','0000000205','UG_0011312','225','N',TIMESTAMP '2022-05-06 09:35:00','61003745','0997100','SAA','EIAM',TIMESTAMP '2022-05-10 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000030428','0000000205','UG_0011296','110','Y',TIMESTAMP '2022-05-02 16:58:00','61003745','0997100','SAA','EIAM',TIMESTAMP '2022-05-02 17:00:23','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000030455','0000000205','UG_0011312','225','N',TIMESTAMP '2022-05-06 15:55:03','61003745','0997100','SAA','EIAM',TIMESTAMP '2022-05-10 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000030433','0000000205','UG_0011312','225','N',TIMESTAMP '2022-05-03 15:01:00','61003745','0997100','SAA','EIAM',TIMESTAMP '2022-05-10 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029430','0000000205','UG_0011298','29','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029431','0000000205','UG_0011299','126','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029432','0000000205','UG_0011300','133','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029433','0000000205','UG_0011301','122','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029434','0000000205','UG_0011302','125','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029435','0000000205','UG_0011303','69','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029436','0000000205','UG_0011304','4','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029437','0000000205','UG_0011305','18','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029438','0000000205','UG_0011306','123','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029439','0000000205','UG_0011307','132','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029440','0000000205','UG_0011308','127','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029441','0000000205','UG_0011309','128','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029442','0000000205','UG_0011310','142','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029443','0000000205','UG_0011311','143','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029444','0000000205','UG_0011312','225','N',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-05-10 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029445','0000000205','UG_0011313','144','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000030432','0000000205','UG_0011312','225','N',TIMESTAMP '2022-05-03 14:30:00','61003745','0997100','SAA','EIAM',TIMESTAMP '2022-05-10 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000030434','0000000205','UG_0011312','225','N',TIMESTAMP '2022-05-03 15:18:00','61003745','0997100','SAA','EIAM',TIMESTAMP '2022-05-10 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029428','0000000205','UG_0011296','110','N',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-05-02 16:15:25','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USER_GROU_role_rtns (USER_GROU_ROLE_RTNS_ID,SYST_ID,USER_GROU_ID,ROLE_NO,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000029429','0000000205','UG_0011297','106','Y',TIMESTAMP '2022-04-19 12:19:19','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:30:24','99999999','9999999','SAA','EIAM') ;
|
||||
|
||||
-- 사용자-그룹관계
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390266','0000000205','09861303','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389612','0000000205','61005896','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390990','0000000205','09861100','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390096','0000000205','09861195','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390103','0000000205','61005899','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390377','0000000205','09861194','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390201','0000000205','09861302','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390396','0000000205','61005872','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390209','0000000205','09861196','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390236','0000000205','61005905','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000391388','0000000205','09861195','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389987','0000000205','61005898','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389376','0000000205','61005886','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000391283','0000000205','09861303','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389378','0000000205','61005873','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389388','0000000205','09861194','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389410','0000000205','09861096','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000391162','0000000205','09861196','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390709','0000000205','61005895','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000391138','0000000205','09861197','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000391142','0000000205','09861102','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389116','0000000205','09861101','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389667','0000000205','09861100','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389185','0000000205','61005895','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000391098','0000000205','09861097','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388336','0000000205','09861097','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388550','0000000205','61005896','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388825','0000000205','61005872','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388839','0000000205','61005886','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388149','0000000205','61005873','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388081','0000000205','09861096','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388097','0000000205','09861102','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388116','0000000205','61005905','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000388229','0000000205','61005899','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389054','0000000205','09861197','UG_0011306',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389005','0000000205','09861302','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000390044','0000000205','09861101','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC_user_grou_rtns (USAC_USER_GROU_RTNS_ID,SYST_ID,PRAF_NO,USER_GROU_ID,VLDT_YMD,EXPR_YMD,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('0000389007','0000000205','61005898','UG_0011307',TIMESTAMP '2022-04-19 00:00:00',TIMESTAMP '9999-12-31 00:00:00','Y',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM',TIMESTAMP '2022-04-19 12:21:14','99999999','9999999','SAA','EIAM') ;
|
||||
|
||||
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861101','김정우','0993129','ZnNnIt2Vpx0VuIFtTTC4X-fRMEUZVxibJyigyjw58Ms=','N08550701','영업지원시스템기획-운영','80SR','Sr','810135','챕터원','Y',TIMESTAMP '2021-04-21 05:04:04','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-07-01 12:34:37','99999999','9999999','SAA','EIAM') ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861102','한준영','0999200','hIX9bJ9IISfan3Gv8RU3huipr7otRghkLqlNgyw8ZVc=','N08580303','내부통제','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:08','99999999','9999999','SAA','0000000002',TIMESTAMP '2023-04-01 12:31:16','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861197','최원진','0995400','QhUqiS4UN6Byc+Z9n7xs8AzRTe4FeArLtMyNHtGGi10=','N08540202','계약심사','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:59','99999999','9999999','SAA','0000000002',TIMESTAMP '2023-04-01 12:30:18','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861096','이석진','0995146','WfydcrzCqws+QIilkz13iXWxHeXTgZ+knGEvxuzUFjE=','N08620101','고객서비스 업무지원','80SR','Sr','810114','CS지원','Y',TIMESTAMP '2021-04-21 05:04:59','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-05-01 12:34:43','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861097','김남중','0993203','wd12hSaDEPA5UbaPxrPaCZqHO-w23cd7WLhkzHz01+s=','N08550803','제휴마케팅기획관리','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:10','99999999','9999999','SAA','0000000002',TIMESTAMP '2024-10-10 15:41:26','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861194','윤승로','0993134','XuVpEqWforwy4JgbLOOO7Qc3OTMM2uXFucCXgXAKPec=','N08550209','FC교육운영','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:59','99999999','9999999','SAA','0000000002',TIMESTAMP '2024-11-01 12:41:52','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861303','박범진','0999200','MTa1Jvmkzhwk+-5cnsORvnXAwLCyWYs5tiPa9xgVrPQ=','N08550301','GA채널기획관리','80MST','Mst','810122','파트원','Y',TIMESTAMP '2021-04-21 05:04:01','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-01-01 12:31:03','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861302','문준호','0995400','XafkrGHaWLTxackt7T2NsjCAUXBF3uyjMNAXVyUSvC4=','N08560301','소비자보호기획','80SR','Sr','810122','파트원','Y',TIMESTAMP '2021-04-21 05:04:08','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-02-01 12:30:54','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861100','윤희준','0993203','X-4VwtIV1YCBR6+0UqZBVHqk9GpqHy3AMDJYKihWQhk=','N08560101','계약관리','80SR','Sr','810122','파트원','Y',TIMESTAMP '2021-04-21 05:04:04','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-02-01 12:30:55','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861195','박준영','0993203','tyg5izJmKkXYqIa6YfKp0Y42yIUjrhY+-6hgYZ8Vtmo=','N08510105','부서총괄','80MGR','Mgr','810015','팀장','Y',TIMESTAMP '2021-04-21 05:04:02','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-01-01 12:30:55','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('09861196','정재용','0993129','wPIIdQ3PwXlj5fxgtjWzkZs4yagmbAC9kDU5dNq6sKw=','N08520101','고객DB운영','80SR','Sr','810135','챕터원','Y',TIMESTAMP '2021-04-21 05:04:04','99999999','9999999','SAA','0000000002',TIMESTAMP '2024-10-10 15:41:11','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005872','동영예','0993129','bgHjYQyZochoxQXjI2stZf4dmHANf7r514SxeGnHJL0=','N08550203','FC채널지원','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:04','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-08-01 12:39:02','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005873','동한름','0993129','Vip4QCNT4HYfB9Pa86g7XsEw+LBu02H5BlbDo-W7eQw=','N08580101','내부감사','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:05','99999999','9999999','SAA','0000000002',TIMESTAMP '2024-10-10 15:41:14','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005886','빈길들','0993134','yPGgtuE14m+po6oyB9CfxEIilfrEzPxfi-xN0LXG4t8=','N08560302','고객민원관리','80SR','Sr','810122','파트원','Y',TIMESTAMP '2021-04-21 05:04:04','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-02-01 12:31:08','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005895','오해나','0993134','YNscG5QLZ1B8KIrBUIeGlK8otbz94A1RJdhh8mRNlGA=','N08900804','육아휴직','80SR','Sr','810095','기타','Y',TIMESTAMP '2021-04-21 05:04:03','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-09-01 12:34:52','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005896','동복수','0993134','BMRwD1vs0bmNdZBBs5VBMq1V0OUTH7AeQdKHwUQvQqw=','N08540302','보험금심사기획-지원','80MGR','Mgr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:05','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-06-01 12:31:09','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005898','동권문','0995400','jyBPF4WMlcE8On8jG9XiWYlV92t5f1MkGKRe2N+xZ+8=','N08540202','계약심사','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:05','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-01-01 12:31:51','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005899','심복','0995400','pH9Da+rtRLXLu9oKplGsqvcrF5rL5N8ZVfInlRMa-Vs=','N08550302','GA채널지원','80SR','Sr','810030','팀원','Y',TIMESTAMP '2021-04-21 05:04:02','99999999','9999999','SAA','0000000002',TIMESTAMP '2025-01-01 12:31:11','99999999','9999999','SAA','EIAM' ) ;
|
||||
INSERT INTO S_EIAM_TIS.AA_USAC (PRAF_NO,PRAF_NM,OGNZ_NO,ADDRE,PRAF_OFDU_CD,PRAF_OFDU_NM,PRAF_OFLE_CD,PRAF_OFLE_NM,PRAF_DUTY_CD,PRAF_DUTY_NM,PUSE_YN,SYST_RGI_DT,SYST_RGI_PRAF_NO,SYST_RGI_OGNZ_NO,SYST_RGI_SYST_CD,SYST_RGI_PRGR_ID,SYST_CHG_DT,SYST_CHG_PRAF_NO,SYST_CHG_OGNZ_NO,SYST_CHG_SYST_CD,SYST_CHG_PRGR_ID) VALUES ('61005905','모중제','0995400','+GR4ai78TrPdOiSDkJ+SbjCiwWQfO9QbSdKD7Kydpis=','N08560507','고객서비스지원','80MGR','Mgr','810122','파트원','Y',TIMESTAMP '2021-04-21 05:04:09','99999999','9999999','SAA','0000000002',TIMESTAMP '2024-10-10 15:46:12','99999999','9999999','SAA','EIAM' ) ;
|
||||
|
||||
|
||||
INSERT INTO S_EIAM_TIS.AA_OGNZ (OGNZ_NM, OGNZ_ABR_NM, OGNZ_NO, SPPO_GONZ_NO, VLDT_YMD, PUSE_YN, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES ('총무팀', '총무팀', '0993129', 'shinhanlife@shinhanlife.com', '20260101', 'Y', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001');
|
||||
INSERT INTO S_EIAM_TIS.AA_OGNZ (OGNZ_NM, OGNZ_ABR_NM, OGNZ_NO, SPPO_GONZ_NO, VLDT_YMD, PUSE_YN, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES ('재무팀', '재무팀', '0999200', 'shinhanlife@shinhanlife.com', '20260101', 'Y', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001');
|
||||
INSERT INTO S_EIAM_TIS.AA_OGNZ (OGNZ_NM, OGNZ_ABR_NM, OGNZ_NO, SPPO_GONZ_NO, VLDT_YMD, PUSE_YN, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES ('개발팀', '개발팀', '0995400', 'shinhanlife@shinhanlife.com', '20260101', 'Y', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001');
|
||||
INSERT INTO S_EIAM_TIS.AA_OGNZ (OGNZ_NM, OGNZ_ABR_NM, OGNZ_NO, SPPO_GONZ_NO, VLDT_YMD, PUSE_YN, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES ('구매팀', '구매팀', '0995146', 'shinhanlife@shinhanlife.com', '20260101', 'Y', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001');
|
||||
INSERT INTO S_EIAM_TIS.AA_OGNZ (OGNZ_NM, OGNZ_ABR_NM, OGNZ_NO, SPPO_GONZ_NO, VLDT_YMD, PUSE_YN, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES ('AI사업부', 'AI사업부', '0993203', 'shinhanlife@shinhanlife.com', '20260101', 'Y', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001');
|
||||
INSERT INTO S_EIAM_TIS.AA_OGNZ (OGNZ_NM, OGNZ_ABR_NM, OGNZ_NO, SPPO_GONZ_NO, VLDT_YMD, PUSE_YN, SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID, SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID) VALUES ('보안팀', '보안팀', '0993134', 'shinhanlife@shinhanlife.com', '20260101', 'Y', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001', TIMESTAMP '2023-01-01 12:00:00', 'A1234567', '7654321', 'SYS', 'PROG001');
|
||||
36
dap-gateway/src/main/resources/logback-spring.xml
Normal file
36
dap-gateway/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<!-- 1. 로그 패턴 설정 (MDC traceId 포함) -->
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n" />
|
||||
|
||||
<!-- 2. 콘솔(Console) 출력 설정 (로컬 개발용) -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 3. 파일(File) 출력 설정 (서버 운영용) -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/axhub-gateway.log</file> <!-- 현재 로그가 쌓이는 파일 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 매일 자정에 날짜를 붙여서 지난 로그를 분리 저장 -->
|
||||
<fileNamePattern>logs/axhub-gateway-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 최대 30일치 로그만 보관하고 오래된 것은 자동 삭제 -->
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 4. 기본 로깅 레벨 설정 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
|
||||
<!-- 5. 우리 프로젝트 패키지는 디버그 레벨까지 상세히 보기 -->
|
||||
<logger name="io.shinhanlife" level="DEBUG" />
|
||||
</configuration>
|
||||
193
dap-gateway/src/main/resources/mapper/AccessMgmtMapper.xml
Normal file
193
dap-gateway/src/main/resources/mapper/AccessMgmtMapper.xml
Normal file
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="io.shinhanlife.axhub.biz.so.atm.domain.repository.AccessMgmtMapper">
|
||||
|
||||
<resultMap id="roleResultMap" type="io.shinhanlife.axhub.biz.so.atm.dto.RoleListOutDto">
|
||||
<result column="SYST_ID" property="systId"/>
|
||||
<result column="ROLE_NO" property="roleNo"/>
|
||||
<result column="ROLE_NM" property="roleNm"/>
|
||||
<result column="ROLE_DS" property="roleDs"/>
|
||||
<result column="PUSE_YN" property="puseYn"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="athrItemResultMap" type="io.shinhanlife.axhub.biz.so.atm.dto.AthrItemOutDto">
|
||||
<result column="RESOURCE_ID" property="resourceId"/>
|
||||
<result column="RESOURCE_NM" property="resourceNm"/>
|
||||
<result column="RESOURCE_DS" property="resourceDs"/>
|
||||
<result column="TYPE_CODE" property="typeCode"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 역할 목록 조회 -->
|
||||
<select id="selectRoles" parameterType="io.shinhanlife.axhub.biz.so.atm.dto.RoleListInDto"
|
||||
resultMap="roleResultMap">
|
||||
SELECT
|
||||
SYST_ID,
|
||||
ROLE_NO,
|
||||
ROLE_NM,
|
||||
ROLE_DS,
|
||||
PUSE_YN
|
||||
FROM
|
||||
S_EIAM_TIS.AA_ROLE
|
||||
<where>
|
||||
<if test="systId != null and systId != ''">
|
||||
AND SYST_ID = #{systId}
|
||||
</if>
|
||||
<if test="puseYn != null and puseYn != ''">
|
||||
AND PUSE_YN = #{puseYn}
|
||||
</if>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND ROLE_NM LIKE '%' || #{keyword} || '%'
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY ROLE_NM ASC
|
||||
</select>
|
||||
|
||||
<!-- Tool 마스터 전체 조회 -->
|
||||
<select id="selectAllTools" resultMap="athrItemResultMap">
|
||||
SELECT
|
||||
TOOL_ID AS RESOURCE_ID,
|
||||
TOOL_NM AS RESOURCE_NM,
|
||||
TOOL_DS AS RESOURCE_DS,
|
||||
TOOL_TYPE_CD AS TYPE_CODE
|
||||
FROM
|
||||
AX_TOOL_MST
|
||||
WHERE
|
||||
PUSE_YN = 'Y'
|
||||
ORDER BY
|
||||
TOOL_NM ASC
|
||||
</select>
|
||||
|
||||
<!-- 지식 마스터 전체 조회 -->
|
||||
<select id="selectAllKnwls" resultMap="athrItemResultMap">
|
||||
SELECT
|
||||
KNWL_ID AS RESOURCE_ID,
|
||||
KNWL_NM AS RESOURCE_NM,
|
||||
KNWL_DS AS RESOURCE_DS,
|
||||
KNWL_TYPE_CD AS TYPE_CODE
|
||||
FROM
|
||||
AX_KNWL_MST
|
||||
WHERE
|
||||
PUSE_YN = 'Y'
|
||||
ORDER BY
|
||||
KNWL_NM ASC
|
||||
</select>
|
||||
|
||||
<!-- 역할에 부여된 Tool ID 목록 -->
|
||||
<select id="selectGrantedToolIds" parameterType="io.shinhanlife.axhub.biz.so.atm.dto.AthrSearchInDto"
|
||||
resultType="string">
|
||||
SELECT
|
||||
TOOL_ID
|
||||
FROM
|
||||
AX_ROLE_TOOL_ATHR
|
||||
WHERE
|
||||
SYST_ID = #{systId}
|
||||
AND ROLE_NO = #{roleNo}
|
||||
AND PUSE_YN = 'Y'
|
||||
</select>
|
||||
|
||||
<!-- 역할에 부여된 지식 ID 목록 -->
|
||||
<select id="selectGrantedKnwlIds" parameterType="io.shinhanlife.axhub.biz.so.atm.dto.AthrSearchInDto"
|
||||
resultType="string">
|
||||
SELECT
|
||||
KNWL_ID
|
||||
FROM
|
||||
AX_ROLE_KNWL_ATHR
|
||||
WHERE
|
||||
SYST_ID = #{systId}
|
||||
AND ROLE_NO = #{roleNo}
|
||||
AND PUSE_YN = 'Y'
|
||||
</select>
|
||||
|
||||
<!-- Tool 권한 전체 삭제 -->
|
||||
<delete id="deleteToolAthr">
|
||||
DELETE FROM AX_ROLE_TOOL_ATHR
|
||||
WHERE
|
||||
SYST_ID = #{systId}
|
||||
AND ROLE_NO = #{roleNo}
|
||||
</delete>
|
||||
|
||||
<!-- Tool 권한 단건 삽입 -->
|
||||
<insert id="insertToolAthr" parameterType="io.shinhanlife.axhub.biz.so.atm.dto.RoleToolAthrInDto">
|
||||
INSERT INTO AX_ROLE_TOOL_ATHR (
|
||||
ROLE_TOOL_ATHR_ID,
|
||||
SYST_ID,
|
||||
ROLE_NO,
|
||||
TOOL_ID,
|
||||
PUSE_YN,
|
||||
SYST_RGI_DT,
|
||||
SYST_RGI_PRAF_NO,
|
||||
SYST_RGI_OGNZ_NO,
|
||||
SYST_RGI_SYST_CD,
|
||||
SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT,
|
||||
SYST_CHG_PRAF_NO,
|
||||
SYST_CHG_OGNZ_NO,
|
||||
SYST_CHG_SYST_CD,
|
||||
SYST_CHG_PRGR_ID
|
||||
) VALUES (
|
||||
#{roleToolAthrId},
|
||||
#{systId},
|
||||
#{roleNo},
|
||||
#{toolId},
|
||||
#{puseYn},
|
||||
#{systRgiDt},
|
||||
#{systRgiPrafNo},
|
||||
#{systRgiOgnzNo},
|
||||
#{systRgiSystCd},
|
||||
#{systRgiPrgrId},
|
||||
#{systChgDt},
|
||||
#{systChgPrafNo},
|
||||
#{systChgOgnzNo},
|
||||
#{systChgSystCd},
|
||||
#{systChgPrgrId}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 지식 권한 전체 삭제 -->
|
||||
<delete id="deleteKnwlAthr">
|
||||
DELETE FROM AX_ROLE_KNWL_ATHR
|
||||
WHERE
|
||||
SYST_ID = #{systId}
|
||||
AND ROLE_NO = #{roleNo}
|
||||
</delete>
|
||||
|
||||
<!-- 지식 권한 단건 삽입 -->
|
||||
<insert id="insertKnwlAthr" parameterType="io.shinhanlife.axhub.biz.so.atm.dto.RoleKnwlAthrInDto">
|
||||
INSERT INTO AX_ROLE_KNWL_ATHR (
|
||||
ROLE_KNWL_ATHR_ID,
|
||||
SYST_ID,
|
||||
ROLE_NO,
|
||||
KNWL_ID,
|
||||
PUSE_YN,
|
||||
SYST_RGI_DT,
|
||||
SYST_RGI_PRAF_NO,
|
||||
SYST_RGI_OGNZ_NO,
|
||||
SYST_RGI_SYST_CD,
|
||||
SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT,
|
||||
SYST_CHG_PRAF_NO,
|
||||
SYST_CHG_OGNZ_NO,
|
||||
SYST_CHG_SYST_CD,
|
||||
SYST_CHG_PRGR_ID
|
||||
) VALUES (
|
||||
#{roleKnwlAthrId},
|
||||
#{systId},
|
||||
#{roleNo},
|
||||
#{knwlId},
|
||||
#{puseYn},
|
||||
#{systRgiDt},
|
||||
#{systRgiPrafNo},
|
||||
#{systRgiOgnzNo},
|
||||
#{systRgiSystCd},
|
||||
#{systRgiPrgrId},
|
||||
#{systChgDt},
|
||||
#{systChgPrafNo},
|
||||
#{systChgOgnzNo},
|
||||
#{systChgSystCd},
|
||||
#{systChgPrgrId}
|
||||
)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
46
dap-gateway/src/main/resources/mapper/ZtUsacRepository.xml
Normal file
46
dap-gateway/src/main/resources/mapper/ZtUsacRepository.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.shinhanlife.axhub.common.session.domain.repository.ZtUsacRepository">
|
||||
|
||||
<select id="selectZtUsac" resultType="io.shinhanlife.axhub.common.session.dto.ZtUsacOutDto">
|
||||
|
||||
SELECT
|
||||
AU.PRAF_NO, -- 인사번호
|
||||
AU.PRAF_NM, -- 인사명
|
||||
AU.OGNZ_NO, -- 조직번호
|
||||
OG.OGNZ_NM, -- 조직명
|
||||
AU.ADDRE, -- 이메일주소
|
||||
AU.PRAF_OFDU_CD, -- 인사직무코드
|
||||
AU.PRAF_OFDU_NM, -- 인사직무명
|
||||
AU.PRAF_OFLE_CD, -- 인사직급코드
|
||||
AU.PRAF_OFLE_NM, -- 인사직급명
|
||||
AU.PRAF_DUTY_CD, -- 인사직책코드
|
||||
AU.PRAF_DUTY_NM, -- 인사직책명
|
||||
AU.MNGR_PRAF_NO, -- 관리자인사번호
|
||||
AU.PUSE_YN, -- 사용여부
|
||||
LISTAGG(URR.ROLE_NO, ',') WITHIN GROUP (ORDER BY URR.ROLE_NO) AS ROLE_NO_STR_LIST,
|
||||
LISTAGG(UR.ROLE_NM, ',') WITHIN GROUP (ORDER BY URR.ROLE_NO) AS ROLE_NM_STR_LIST,
|
||||
LISTAGG(AM.TGTR_PRAF_NO, ',') WITHIN GROUP (ORDER BY AM.TGTR_PRAF_NO) AS TGTR_PRAF_NO_STR_LIST,
|
||||
LISTAGG(AM.TGTR_OGNZ_NO, ',') WITHIN GROUP (ORDER BY AM.TGTR_PRAF_NO) AS TGTR_OGNZ_NO_STR_LIST
|
||||
FROM
|
||||
S_EIAM_TIS.AA_USAC AU
|
||||
INNER JOIN
|
||||
S_EIAM_TIS.AA_USAC_USER_GROU_RTNS UGR ON AU.PRAF_NO = UGR.PRAF_NO
|
||||
INNER JOIN
|
||||
S_EIAM_TIS.AA_USER_GROU UG ON UGR.USER_GROU_ID = UG.USER_GROU_ID
|
||||
INNER JOIN
|
||||
S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS URR ON UG.USER_GROU_ID = URR.USER_GROU_ID
|
||||
INNER JOIN
|
||||
S_EIAM_TIS.AA_ROLE UR ON URR.ROLE_NO = UR.ROLE_NO
|
||||
LEFT OUTER JOIN
|
||||
S_EIAM_TIS.AA_MNDT AM ON AM.PRAF_NO = AU.PRAF_NO
|
||||
INNER JOIN
|
||||
S_EIAM_TIS.AA_OGNZ OG ON OG.OGNZ_NO = AU.OGNZ_NO
|
||||
WHERE
|
||||
AU.PRAF_NO = #{prafNo}
|
||||
GROUP BY
|
||||
AU.PRAF_NO, AU.PRAF_NM, AU.OGNZ_NO, AU.ADDRE, AU.PRAF_OFDU_CD, AU.PRAF_OFDU_NM, AU.PRAF_OFLE_CD, AU.PRAF_OFLE_NM, AU.PRAF_DUTY_CD, AU.PRAF_DUTY_NM, AU.MNGR_PRAF_NO, AU.PUSE_YN;
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.shinhanlife.axhub.biz.sm.mmg.domain.repository.MenuRepository">
|
||||
|
||||
<select id="selectMenu" resultType="io.shinhanlife.axhub.biz.sm.mmg.dto.MenuOutDto">
|
||||
SELECT MENU_ID
|
||||
, PARENT_MENU_ID
|
||||
, NAME
|
||||
, PATH
|
||||
, TITLE
|
||||
, ICON
|
||||
, IS_HIDE
|
||||
, IS_HIDE_TAB
|
||||
, LINK
|
||||
, IS_IFRAME
|
||||
, KEEP_ALIVE
|
||||
, ORDER_SEQ
|
||||
, IS_ACTIVE
|
||||
FROM S_TIS.ZT_MENU
|
||||
WHERE 1=1
|
||||
<if test="isActive != null and isActive != ''">
|
||||
AND IS_ACTIVE = #{isActive}
|
||||
</if>
|
||||
<if test="path != null and path != ''">
|
||||
AND PATH LIKE CONCAT('%', #{path}, '%')
|
||||
</if>
|
||||
<if test="title != null and title != ''">
|
||||
AND TITLE LIKE CONCAT('%', #{title}, '%')
|
||||
</if>
|
||||
ORDER BY ORDER_SEQ ASC
|
||||
</select>
|
||||
|
||||
<select id="selectMenuById" resultType="io.shinhanlife.axhub.biz.sm.mmg.dto.MenuOutDto">
|
||||
SELECT MENU_ID
|
||||
, PARENT_MENU_ID
|
||||
, NAME
|
||||
, PATH
|
||||
, TITLE
|
||||
, ICON
|
||||
, IS_HIDE
|
||||
, IS_HIDE_TAB
|
||||
, LINK
|
||||
, IS_IFRAME
|
||||
, KEEP_ALIVE
|
||||
, ORDER_SEQ
|
||||
, IS_ACTIVE
|
||||
FROM S_TIS.ZT_MENU
|
||||
WHERE MENU_ID = #{menuId}
|
||||
</select>
|
||||
|
||||
<insert id="insertMenu"
|
||||
parameterType="io.shinhanlife.axhub.biz.sm.mmg.dto.MenuSaveInDto"
|
||||
useGeneratedKeys="true"
|
||||
keyProperty="menuId">
|
||||
INSERT INTO S_TIS.ZT_MENU (
|
||||
PARENT_MENU_ID, NAME, PATH, TITLE, ICON,
|
||||
IS_HIDE, IS_HIDE_TAB, LINK, IS_IFRAME, KEEP_ALIVE,
|
||||
ORDER_SEQ, IS_ACTIVE,
|
||||
SYST_RGI_DT, SYST_RGI_PRAF_NO, SYST_RGI_OGNZ_NO, SYST_RGI_SYST_CD, SYST_RGI_PRGR_ID,
|
||||
SYST_CHG_DT, SYST_CHG_PRAF_NO, SYST_CHG_OGNZ_NO, SYST_CHG_SYST_CD, SYST_CHG_PRGR_ID
|
||||
) VALUES (
|
||||
#{parentMenuId}, #{name}, #{path}, #{title}, #{icon},
|
||||
#{isHide}, #{isHideTab}, #{link}, #{isIframe}, #{keepAlive},
|
||||
#{orderSeq}, #{isActive},
|
||||
#{systRgiDt}, #{systRgiPrafNo}, #{systRgiOgnzNo}, #{systRgiSystCd}, #{systRgiPrgrId},
|
||||
#{systChgDt}, #{systChgPrafNo}, #{systChgOgnzNo}, #{systChgSystCd}, #{systChgPrgrId}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateMenu" parameterType="io.shinhanlife.axhub.biz.sm.mmg.dto.MenuSaveInDto">
|
||||
UPDATE S_TIS.ZT_MENU
|
||||
SET
|
||||
PARENT_MENU_ID = #{parentMenuId},
|
||||
NAME = #{name},
|
||||
PATH = #{path},
|
||||
TITLE = #{title},
|
||||
ICON = #{icon},
|
||||
IS_HIDE = #{isHide},
|
||||
IS_HIDE_TAB = #{isHideTab},
|
||||
LINK = #{link},
|
||||
IS_IFRAME = #{isIframe},
|
||||
KEEP_ALIVE = #{keepAlive},
|
||||
ORDER_SEQ = #{orderSeq},
|
||||
IS_ACTIVE = #{isActive},
|
||||
SYST_CHG_DT = #{systChgDt},
|
||||
SYST_CHG_PRAF_NO = #{systChgPrafNo},
|
||||
SYST_CHG_OGNZ_NO = #{systChgOgnzNo},
|
||||
SYST_CHG_SYST_CD = #{systChgSystCd},
|
||||
SYST_CHG_PRGR_ID = #{systChgPrgrId}
|
||||
WHERE MENU_ID = #{menuId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteMenu">
|
||||
DELETE FROM S_TIS.ZT_MENU
|
||||
WHERE MENU_ID = #{menuId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
99
dap-gateway/src/main/resources/mock-responses.json
Normal file
99
dap-gateway/src/main/resources/mock-responses.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"BILL_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "청구 심사 상태 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"billingStatus": "PROCESSING",
|
||||
"expectedCompletionDate": "2026-07-05"
|
||||
}
|
||||
},
|
||||
"BILL_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "청구 처리가 완료되었습니다.",
|
||||
"data": {
|
||||
"processId": "PRC-998811",
|
||||
"result": "APPROVED"
|
||||
}
|
||||
},
|
||||
"BOND_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "디지털 증권 발행 가능 한도 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"availableLimit": 500000000,
|
||||
"currency": "KRW"
|
||||
}
|
||||
},
|
||||
"BOND_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "디지털 증권 발행이 성공적으로 완료되었습니다.",
|
||||
"data": {
|
||||
"bondId": "BND-2026-07-04-1234",
|
||||
"issuedAmount": 10000000
|
||||
}
|
||||
},
|
||||
"HR_VAC_01": {
|
||||
"status": "SUCCESS",
|
||||
"message": "연차 휴가 등록이 완료되었습니다.",
|
||||
"data": {
|
||||
"vacationId": "VAC-8877",
|
||||
"status": "APPROVED"
|
||||
}
|
||||
},
|
||||
"HR_VAC_02": {
|
||||
"status": "SUCCESS",
|
||||
"message": "잔여 연차 일수 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"totalDays": 15,
|
||||
"usedDays": 3,
|
||||
"remainingDays": 12
|
||||
}
|
||||
},
|
||||
"COM_SMS_01": {
|
||||
"status": "SUCCESS",
|
||||
"message": "SMS 발송이 성공적으로 완료되었습니다.",
|
||||
"data": {
|
||||
"messageId": "SMS-11223344",
|
||||
"sentTime": "2026-07-04 10:00:00"
|
||||
}
|
||||
},
|
||||
"COM_EML_01": {
|
||||
"status": "SUCCESS",
|
||||
"message": "이메일 발송이 성공적으로 완료되었습니다.",
|
||||
"data": {
|
||||
"emailId": "EML-998877",
|
||||
"sentTime": "2026-07-04 10:00:00"
|
||||
}
|
||||
},
|
||||
"CNTR_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "계약 상태 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"contractStatus": "ACTIVE",
|
||||
"startDate": "2024-01-01"
|
||||
}
|
||||
},
|
||||
"CNTR_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "계약 상세 내역 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"productName": "신한 라이프 스마트 연금보험",
|
||||
"monthlyPremium": 500000
|
||||
}
|
||||
},
|
||||
"CRM_001": {
|
||||
"status": "SUCCESS",
|
||||
"message": "고객 등급 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"customerGrade": "VIP",
|
||||
"loyaltyPoints": 15000
|
||||
}
|
||||
},
|
||||
"CRM_002": {
|
||||
"status": "SUCCESS",
|
||||
"message": "고객 상세 정보 조회가 완료되었습니다.",
|
||||
"data": {
|
||||
"address": "서울특별시 중구 세종대로 9",
|
||||
"phoneNumber": "010-XXXX-XXXX"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.shinhanlife.axhub.sample.domain.repository.GlowSampleRepository">
|
||||
|
||||
<select id="selectStrnTerm" parameterType="string" resultType="io.shinhanlife.axhub.sample.dto.StrnTermListOutDTO$StrnTerm">
|
||||
SELECT /*
|
||||
--------------------------------------------------------------------------
|
||||
-- 프로그램 설명 : 표준영문약어명조회
|
||||
-- 프로그램명 :
|
||||
--------------------------------------------------------------------------
|
||||
*/
|
||||
'한글명' AS strnTermHanNm,
|
||||
'영문약어' AS strnTermEngcAbrNm,
|
||||
'영문명' AS strnTermEngNm,
|
||||
'화면표기명' AS strnTermScrnTermNm
|
||||
FROM DUAL
|
||||
WHERE '한글명' LIKE '%' || NVL(#{strnTermHanNm}, '') || '%'
|
||||
</select>
|
||||
|
||||
<insert id="insertAppliSystNtfyPati" parameterType="io.shinhanlife.axhub.sample.domain.model.AppliSystNtfyPatiModel">
|
||||
INSERT /*
|
||||
--------------------------------------------------------------------------
|
||||
-- 업무파트명 : 프레임워크
|
||||
-- 프로그램 한글명 또는 화면명 : 어플리케이션시스템알림등록
|
||||
-- 프로그램명 : AppliSystNtfyPati
|
||||
-------------------------------------------------------------------------- */
|
||||
INTO S_TFG.GL_APPLI_SYST_NTFY_PATI
|
||||
( APPLI_SYST_NTFY_PATI_ID
|
||||
,APPLI_SYST_NTFY_KD_CD
|
||||
,APPLI_DUTJ_CD
|
||||
,APPLI_DUTJ_PRJC_CD
|
||||
,NTFY_MSG_CT
|
||||
,NTFY_OCC_DT
|
||||
,NTFY_CFM_DT
|
||||
,NTFY_TRGT_PRAF_NO
|
||||
,SHMS_PML_YN
|
||||
,NTFY_CFM_YN
|
||||
,SYST_RGI_DT
|
||||
,SYST_RGI_PRAF_NO
|
||||
,SYST_RGI_OGNZ_NO
|
||||
,SYST_RGI_SYST_CD
|
||||
,SYST_RGI_PRGR_ID
|
||||
,SYST_CHG_DT
|
||||
,SYST_CHG_PRAF_NO
|
||||
,SYST_CHG_OGNZ_NO
|
||||
,SYST_CHG_SYST_CD
|
||||
,SYST_CHG_PRGR_ID
|
||||
) VALUES (
|
||||
#{appliSystNtfyPatiId}
|
||||
,NVL(#{appliSystNtfyKdCd},'ZZ')
|
||||
,NVL(#{appliDutjCd},'ZZ')
|
||||
,NVL(#{appliDutjPrjcCd},'ZZ')
|
||||
,#{ntfyMsgCt}
|
||||
,TO_DATE(#{ntfyOccDt}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
,TO_DATE(#{ntfyCfmDt}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
,#{ntfyTrgtPrafNo}
|
||||
,#{shmsPmlYn}
|
||||
,#{ntfyCfmYn}
|
||||
,SYSDATE
|
||||
,#{systRgiPrafNo}
|
||||
,#{systRgiOgnzNo}
|
||||
,#{systRgiSystCd}
|
||||
,#{systRgiPrgrId}
|
||||
,SYSDATE
|
||||
,#{systChgPrafNo}
|
||||
,#{systChgOgnzNo}
|
||||
,#{systChgSystCd}
|
||||
,#{systChgPrgrId}
|
||||
)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
618
dap-gateway/src/main/resources/schema.sql
Normal file
618
dap-gateway/src/main/resources/schema.sql
Normal file
@@ -0,0 +1,618 @@
|
||||
CREATE SCHEMA IF NOT EXISTS S_TIS;
|
||||
CREATE SCHEMA IF NOT EXISTS S_EIAM_TIS;
|
||||
SET SCHEMA S_TIS;
|
||||
|
||||
-- ZT_통합코드
|
||||
CREATE TABLE ZT_UNFC_CD (
|
||||
UNFC_CD_ID VARCHAR(50) NOT NULL,
|
||||
UNFC_CD_NM VARCHAR(100),
|
||||
UNFC_CD_HAN_NM VARCHAR(100),
|
||||
UNFC_CD_ENG_NM VARCHAR(100),
|
||||
UNFC_CD_DS VARCHAR(4000),
|
||||
META_SYST_CD VARCHAR(3) NOT NULL,
|
||||
META_DUTJ_TYPE_CD VARCHAR(5) NOT NULL,
|
||||
PUSE_YN VARCHAR(1) NOT NULL,
|
||||
SPPO_UNFC_CD_ID VARCHAR(50),
|
||||
DATA_LOAD_DT DATE,
|
||||
SYST_RGI_DT DATE NOT NULL,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8) NOT NULL,
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7) NOT NULL,
|
||||
SYST_RGI_SYST_CD VARCHAR(3) NOT NULL,
|
||||
SYST_RGI_PRGR_ID VARCHAR(100) NOT NULL,
|
||||
SYST_CHG_DT DATE NOT NULL,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8) NOT NULL,
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7) NOT NULL,
|
||||
SYST_CHG_SYST_CD VARCHAR(3) NOT NULL,
|
||||
SYST_CHG_PRGR_ID VARCHAR(100) NOT NULL,
|
||||
PRIMARY KEY (UNFC_CD_ID)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE ZT_UNFC_CD IS '통합코드';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.UNFC_CD_ID IS '통합코드ID (PK)';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.UNFC_CD_NM IS '통합코드명';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.UNFC_CD_HAN_NM IS '통합코드한글명';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.UNFC_CD_ENG_NM IS '통합코드영문명';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.UNFC_CD_DS IS '통합코드설명';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.META_SYST_CD IS '메타시스템코드';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.META_DUTJ_TYPE_CD IS '메타업무유형코드';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.PUSE_YN IS '사용여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SPPO_UNFC_CD_ID IS '상위통합코드ID';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.DATA_LOAD_DT IS '데이터적재일시';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN ZT_UNFC_CD.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
-- ZT_통합코드 상세
|
||||
CREATE TABLE S_TIS.ZT_UNFC_CD_DET
|
||||
(
|
||||
UNFC_CD_ID VARCHAR(50) NOT NULL,
|
||||
CD_VLDT_VALU VARCHAR(50) NOT NULL,
|
||||
CD_VLDT_VALU_INQR_SEQ NUMBER(5) NULL,
|
||||
CD_VLDT_VALU_NM VARCHAR(400) NULL,
|
||||
CD_VLDT_VALU_HNGL_ABR_NM VARCHAR(200) NULL,
|
||||
CD_VLDT_VALU_ENGC_ABR_NM VARCHAR(200) NULL,
|
||||
CD_VLDT_VALU_HAN_NM VARCHAR(400) NULL,
|
||||
CD_VLDT_VALU_ENG_NM VARCHAR(400) NULL,
|
||||
CD_VLDT_VALU_DS VARCHAR(4000) NULL,
|
||||
CD_ASRT_TYPE_BIT_CD VARCHAR(20) NOT NULL,
|
||||
SPPO_CD_VLDT_VALU VARCHAR(50) NULL,
|
||||
SPPO_UNFC_CD_ID VARCHAR(50) NULL,
|
||||
VLDT_STRT_YMD DATE NULL,
|
||||
VLDT_END_YMD DATE NULL,
|
||||
USER_DEF_VALU_N01 VARCHAR(100) NULL,
|
||||
USER_DEF_VALU_N02 VARCHAR(100) NULL,
|
||||
SYST_RGI_DT DATE NOT NULL,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8) NOT NULL,
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7) NOT NULL,
|
||||
SYST_RGI_SYST_CD VARCHAR(3) NOT NULL,
|
||||
SYST_RGI_PRGR_ID VARCHAR(100) NOT NULL,
|
||||
SYST_CHG_DT DATE NOT NULL,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8) NOT NULL,
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7) NOT NULL,
|
||||
SYST_CHG_SYST_CD VARCHAR(3) NOT NULL,
|
||||
SYST_CHG_PRGR_ID VARCHAR(100) NOT NULL,
|
||||
DATA_LOAD_DT DATE NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE S_TIS.ZT_UNFC_CD_DET IS '통합코드 상세';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.UNFC_CD_ID IS '통합코드ID (PK, 부모)';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU IS '코드유효값 (PK)';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_INQR_SEQ IS '코드유효값조회순서';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_NM IS '코드유효값명';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_HNGL_ABR_NM IS '코드유효값한글약어명';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_ENGC_ABR_NM IS '코드유효값영문약어명';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_HAN_NM IS '코드유효값한글명';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_ENG_NM IS '코드유효값영문명';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_VLDT_VALU_DS IS '코드유효값설명';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.CD_ASRT_TYPE_BIT_CD IS '코드분류유형비트코드';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SPPO_CD_VLDT_VALU IS '상위코드유효값';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SPPO_UNFC_CD_ID IS '상위통합코드ID';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.VLDT_STRT_YMD IS '유효시작일자';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.VLDT_END_YMD IS '유효종료일자';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.USER_DEF_VALU_N01 IS '사용자정의값1';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.USER_DEF_VALU_N02 IS '사용자정의값2';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
COMMENT ON COLUMN S_TIS.ZT_UNFC_CD_DET.DATA_LOAD_DT IS '데이터적재일시';
|
||||
|
||||
-------------------------------------------------------------------------------------------
|
||||
-- ZT_MENU 테이블 생성 (메뉴 기본 정보 저장)
|
||||
CREATE TABLE ZT_MENU (
|
||||
MENU_ID INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- 자동 증가 ID (PK)
|
||||
PARENT_MENU_ID INTEGER, -- 부모 메뉴 ID (FK, NULL 가능 for 루트 메뉴)
|
||||
NAME VARCHAR(100), -- 컴포넌트 이름 (옵셔널)
|
||||
PATH VARCHAR(255) NOT NULL, -- 라우트 경로 (필수)
|
||||
TITLE VARCHAR(100) NOT NULL, -- 메뉴 이름 (meta.title, 필수)
|
||||
ICON VARCHAR(50), -- 메뉴 아이콘 (옵셔널)
|
||||
SHOW_BADGE VARCHAR(1) DEFAULT 'N' CHECK (SHOW_BADGE IN ('Y', 'N')), -- 배지 표시 여부 (Y/N, 옵셔널)
|
||||
SHOW_TEXT_BADGE VARCHAR(50), -- 텍스트 배지 표시 (옵셔널)
|
||||
IS_HIDE VARCHAR(1) DEFAULT 'N' CHECK (IS_HIDE IN ('Y', 'N')), -- 메뉴 숨김 여부 (Y/N, 옵셔널)
|
||||
IS_HIDE_TAB VARCHAR(1) DEFAULT 'N' CHECK (IS_HIDE_TAB IN ('Y', 'N')),-- 탭 숨김 여부 (Y/N, 옵셔널)
|
||||
LINK VARCHAR(255), -- 링크 (옵셔널)
|
||||
IS_IFRAME VARCHAR(1) DEFAULT 'N' CHECK (IS_IFRAME IN ('Y', 'N')), -- iframe 여부 (Y/N, 옵셔널)
|
||||
KEEP_ALIVE VARCHAR(1) DEFAULT 'N' CHECK (KEEP_ALIVE IN ('Y', 'N')), -- 캐시 여부 (Y/N, 옵셔널)
|
||||
ORDER_SEQ INTEGER DEFAULT 0 NOT NULL, -- 메뉴 표시 순서 (정렬용, 필수)
|
||||
IS_ACTIVE VARCHAR(1) DEFAULT 'Y' CHECK (IS_ACTIVE IN ('Y', 'N')), -- 활성 여부 (Y/N, 기본 Y)
|
||||
SYST_RGI_DT DATE, -- 시스템등록일시
|
||||
SYST_RGI_PRAF_NO VARCHAR(50), -- 시스템등록인사번호
|
||||
SYST_RGI_OGNZ_NO VARCHAR(50), -- 시스템등록조직번호
|
||||
SYST_RGI_SYST_CD VARCHAR(50), -- 시스템등록시스템코드
|
||||
SYST_RGI_PRGR_ID VARCHAR(50), -- 시스템등록프로그램ID
|
||||
SYST_CHG_DT DATE, -- 시스템변경일시
|
||||
SYST_CHG_PRAF_NO VARCHAR(50), -- 시스템변경인사번호
|
||||
SYST_CHG_OGNZ_NO VARCHAR(50), -- 시스템변경조직번호
|
||||
SYST_CHG_SYST_CD VARCHAR(50), -- 시스템변경시스템코드
|
||||
SYST_CHG_PRGR_ID VARCHAR(50) -- 시스템변경프로그램ID
|
||||
);
|
||||
|
||||
-- 제약 조건 추가
|
||||
ALTER TABLE ZT_MENU ADD CONSTRAINT FK_ZT_MENU_PARENT FOREIGN KEY (PARENT_MENU_ID) REFERENCES ZT_MENU (MENU_ID) ON DELETE CASCADE; -- 부모 삭제 시 자식도 삭제 (계층 삭제 지원)
|
||||
|
||||
-- 인덱스 생성 (조회 최적화)
|
||||
CREATE INDEX IDX_ZT_MENU_PARENT ON ZT_MENU (PARENT_MENU_ID);
|
||||
CREATE INDEX IDX_ZT_MENU_PATH ON ZT_MENU (PATH);
|
||||
|
||||
-- 컬럼 코멘트 추가
|
||||
COMMENT ON COLUMN ZT_MENU.MENU_ID IS '메뉴 ID (PK)';
|
||||
COMMENT ON COLUMN ZT_MENU.PARENT_MENU_ID IS '부모 메뉴 ID (FK)';
|
||||
COMMENT ON COLUMN ZT_MENU.NAME IS '컴포넌트 이름';
|
||||
COMMENT ON COLUMN ZT_MENU.PATH IS '라우트 경로';
|
||||
COMMENT ON COLUMN ZT_MENU.TITLE IS '메뉴 이름';
|
||||
COMMENT ON COLUMN ZT_MENU.ICON IS '메뉴 아이콘';
|
||||
COMMENT ON COLUMN ZT_MENU.SHOW_BADGE IS '배지 표시 여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_MENU.SHOW_TEXT_BADGE IS '텍스트 배지 표시';
|
||||
COMMENT ON COLUMN ZT_MENU.IS_HIDE IS '메뉴 숨김 여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_MENU.IS_HIDE_TAB IS '탭 숨김 여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_MENU.LINK IS '링크';
|
||||
COMMENT ON COLUMN ZT_MENU.IS_IFRAME IS 'iframe 여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_MENU.KEEP_ALIVE IS '캐시 여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_MENU.ORDER_SEQ IS '메뉴 표시 순서';
|
||||
COMMENT ON COLUMN ZT_MENU.IS_ACTIVE IS '활성 여부 (Y/N)';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN ZT_MENU.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
-- ============================================================
|
||||
-- AX HUB 역할-Tool/지식 권한 관리 DDL
|
||||
-- 대상 스키마 : 프로젝트 AP 스키마 (S_EIAM_TIS 는 읽기전용 참조)
|
||||
-- 작성일 : 2026-06-25
|
||||
-- ============================================================
|
||||
|
||||
-- ① Tool 마스터
|
||||
CREATE TABLE AX_TOOL_MST (
|
||||
TOOL_ID VARCHAR(20) NOT NULL, -- Tool ID (PK)
|
||||
TOOL_NM VARCHAR(100) NOT NULL, -- Tool명
|
||||
TOOL_DS VARCHAR(500), -- Tool설명
|
||||
TOOL_TYPE_CD VARCHAR(20), -- Tool유형코드 (MCP / FUNC / EXT)
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',-- 사용여부
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_TOOL_MST PRIMARY KEY (TOOL_ID)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE AX_TOOL_MST IS 'AX HUB Tool 마스터';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_ID IS 'Tool ID (PK)';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_NM IS 'Tool명';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_DS IS 'Tool설명';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.TOOL_TYPE_CD IS 'Tool유형코드 (MCP/FUNC/EXT)';
|
||||
COMMENT ON COLUMN AX_TOOL_MST.PUSE_YN IS '사용여부 (Y/N)';
|
||||
|
||||
|
||||
-- ② 지식(Knowledge) 마스터
|
||||
CREATE TABLE AX_KNWL_MST (
|
||||
KNWL_ID VARCHAR(20) NOT NULL, -- 지식 ID (PK)
|
||||
KNWL_NM VARCHAR(100) NOT NULL, -- 지식명
|
||||
KNWL_DS VARCHAR(500), -- 지식설명
|
||||
KNWL_TYPE_CD VARCHAR(20), -- 지식유형코드 (PRODUCT/MANUAL/LEGAL 등)
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_KNWL_MST PRIMARY KEY (KNWL_ID)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE AX_KNWL_MST IS 'AX HUB 지식 마스터';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_ID IS '지식 ID (PK)';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_NM IS '지식명';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_DS IS '지식설명';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.KNWL_TYPE_CD IS '지식유형코드 (PRODUCT/MANUAL/LEGAL/REPORT)';
|
||||
COMMENT ON COLUMN AX_KNWL_MST.PUSE_YN IS '사용여부 (Y/N)';
|
||||
|
||||
|
||||
-- ③ 역할-Tool 권한 매핑
|
||||
-- SYST_ID + ROLE_NO → S_EIAM_TIS.AA_ROLE 참조 (FK 미설정: 스키마 분리 환경)
|
||||
CREATE TABLE AX_ROLE_TOOL_ATHR (
|
||||
ROLE_TOOL_ATHR_ID VARCHAR(20) NOT NULL, -- 역할Tool권한ID (PK)
|
||||
SYST_ID VARCHAR(10) NOT NULL, -- 시스템ID (EIAM 연동 키)
|
||||
ROLE_NO VARCHAR(50) NOT NULL, -- 역할번호 (EIAM AA_ROLE.ROLE_NO)
|
||||
TOOL_ID VARCHAR(20) NOT NULL, -- Tool ID
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_ROLE_TOOL_ATHR PRIMARY KEY (ROLE_TOOL_ATHR_ID)
|
||||
);
|
||||
|
||||
-- (SYST_ID, ROLE_NO, TOOL_ID) 중복 방지
|
||||
CREATE UNIQUE INDEX UK_AX_ROLE_TOOL_ATHR
|
||||
ON AX_ROLE_TOOL_ATHR (SYST_ID, ROLE_NO, TOOL_ID);
|
||||
|
||||
COMMENT ON TABLE AX_ROLE_TOOL_ATHR IS '역할-Tool 권한 매핑';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.ROLE_TOOL_ATHR_ID IS '역할Tool권한ID (PK)';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.ROLE_NO IS '역할번호 (EIAM)';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.TOOL_ID IS 'Tool ID';
|
||||
COMMENT ON COLUMN AX_ROLE_TOOL_ATHR.PUSE_YN IS '사용여부';
|
||||
|
||||
|
||||
-- ④ 역할-지식 권한 매핑
|
||||
CREATE TABLE AX_ROLE_KNWL_ATHR (
|
||||
ROLE_KNWL_ATHR_ID VARCHAR(20) NOT NULL, -- 역할지식권한ID (PK)
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
ROLE_NO VARCHAR(50) NOT NULL,
|
||||
KNWL_ID VARCHAR(20) NOT NULL,
|
||||
PUSE_YN VARCHAR(1) DEFAULT 'Y',
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100),
|
||||
CONSTRAINT PK_AX_ROLE_KNWL_ATHR PRIMARY KEY (ROLE_KNWL_ATHR_ID)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX UK_AX_ROLE_KNWL_ATHR
|
||||
ON AX_ROLE_KNWL_ATHR (SYST_ID, ROLE_NO, KNWL_ID);
|
||||
|
||||
COMMENT ON TABLE AX_ROLE_KNWL_ATHR IS '역할-지식 권한 매핑';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.ROLE_KNWL_ATHR_ID IS '역할지식권한ID (PK)';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.ROLE_NO IS '역할번호 (EIAM)';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.KNWL_ID IS '지식ID';
|
||||
COMMENT ON COLUMN AX_ROLE_KNWL_ATHR.PUSE_YN IS '사용여부';
|
||||
|
||||
|
||||
|
||||
-- AA_USAC 테이블 생성 (유저 마스터)
|
||||
CREATE TABLE S_EIAM_TIS.AA_USAC (
|
||||
PRAF_NO VARCHAR(8) PRIMARY KEY, -- 인사번호 (PK)
|
||||
PRAF_NM VARCHAR(200), -- 인사명
|
||||
OGNZ_NO VARCHAR(7) NOT NULL, -- 조직번호 (NOT NULL)
|
||||
ADDRE VARCHAR(250), -- 이메일주소
|
||||
PRAF_OFDU_CD VARCHAR(10), -- 인사직무코드
|
||||
PRAF_OFDU_NM VARCHAR(100), -- 인사직무명
|
||||
PRAF_OFLE_CD VARCHAR(10), -- 인사직급코드
|
||||
PRAF_OFLE_NM VARCHAR(100), -- 인사직급명
|
||||
PRAF_DUTY_CD VARCHAR(10), -- 인사직책코드
|
||||
PRAF_DUTY_NM VARCHAR(100), -- 인사직책명
|
||||
MNGR_PRAF_NO VARCHAR(8), -- 관리자인사번호
|
||||
PUSE_YN VARCHAR(1), -- 사용여부
|
||||
SYST_RGI_DT DATE, -- 시스템등록일시
|
||||
SYST_RGI_PRAF_NO VARCHAR(8), -- 시스템등록인사번호
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7), -- 시스템등록조직번호
|
||||
SYST_RGI_SYST_CD VARCHAR(3), -- 시스템등록시스템코드
|
||||
SYST_RGI_PRGR_ID VARCHAR(100), -- 시스템등록프로그램ID
|
||||
SYST_CHG_DT DATE, -- 시스템변경일시
|
||||
SYST_CHG_PRAF_NO VARCHAR(8), -- 시스템변경인사번호
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7), -- 시스템변경조직번호
|
||||
SYST_CHG_SYST_CD VARCHAR(3), -- 시스템변경시스템코드
|
||||
SYST_CHG_PRGR_ID VARCHAR(100) -- 시스템변경프로그램ID
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX IDX_AA_USAC_OGNZ_NO ON S_EIAM_TIS.AA_USAC (OGNZ_NO);
|
||||
|
||||
-- 컬럼 코멘트 추가
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_NO IS '인사번호 (PK)';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_NM IS '인사명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.OGNZ_NO IS '조직번호 (NOT NULL)';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.ADDRE IS '이메일주소';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_OFDU_CD IS '인사직무코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_OFDU_NM IS '인사직무명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_OFLE_CD IS '인사직급코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_OFLE_NM IS '인사직급명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_DUTY_CD IS '인사직책코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PRAF_DUTY_NM IS '인사직책명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.MNGR_PRAF_NO IS '관리자인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.PUSE_YN IS '사용여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
|
||||
|
||||
-- 위임
|
||||
CREATE TABLE S_EIAM_TIS.AA_MNDT
|
||||
(
|
||||
MNDT_ID VARCHAR(10) NOT NULL,
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
PRAF_NO VARCHAR(8) NOT NULL,
|
||||
OGNZ_NO VARCHAR(7),
|
||||
TGTR_PRAF_NO VARCHAR(8) NOT NULL,
|
||||
TGTR_OGNZ_NO VARCHAR(7),
|
||||
ROLE_NO VARCHAR(50) NOT NULL,
|
||||
MNDT_STRT_DT DATE,
|
||||
MNDT_END_DT DATE,
|
||||
MNDT_DS VARCHAR(1000),
|
||||
PUSE_YN VARCHAR(1),
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100)
|
||||
);
|
||||
|
||||
ALTER TABLE S_EIAM_TIS.AA_MNDT ADD CONSTRAINT PK_AA_MNDT PRIMARY KEY (MNDT_ID);
|
||||
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.MNDT_ID IS '위임ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.PRAF_NO IS '인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.OGNZ_NO IS '조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.TGTR_PRAF_NO IS '대상자인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.TGTR_OGNZ_NO IS '대상자조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.ROLE_NO IS '역할번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.MNDT_STRT_DT IS '위임시작일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.MNDT_END_DT IS '위임종료일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.MNDT_DS IS '위임설명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.PUSE_YN IS '사용여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_MNDT.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
-- 역할
|
||||
CREATE TABLE S_EIAM_TIS.AA_ROLE
|
||||
(
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
ROLE_NO VARCHAR(50) NOT NULL,
|
||||
ROLE_NM VARCHAR(100),
|
||||
ROLE_DS VARCHAR(1000),
|
||||
ASST_OWNR_ROLE_TYPE_CD VARCHAR(3),
|
||||
MASK_ECPT_MD_CD VARCHAR(1),
|
||||
INDV_INFO_ATHR_YN VARCHAR(1),
|
||||
MAIN_CST_IFIN_YN VARCHAR(1),
|
||||
PUSE_YN VARCHAR(1),
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX S_EIAM_TIS.PK_AA_ROLE
|
||||
ON S_EIAM_TIS.AA_ROLE (SYST_ID,ROLE_NO);
|
||||
|
||||
ALTER TABLE S_EIAM_TIS.AA_ROLE ADD CONSTRAINT PK_AA_ROLE PRIMARY KEY (SYST_ID,ROLE_NO);
|
||||
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.ROLE_NO IS '역할번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.ROLE_NM IS '역할명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.ROLE_DS IS '역할설명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.ASST_OWNR_ROLE_TYPE_CD IS '자산소유자역할유형코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.MASK_ECPT_MD_CD IS '마스킹제외방법코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.INDV_INFO_ATHR_YN IS '개인정보권한여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.MAIN_CST_IFIN_YN IS '주요고객정보조회여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.PUSE_YN IS '사용여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_ROLE.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
|
||||
-- 사용자그룹
|
||||
CREATE TABLE S_EIAM_TIS.AA_USER_GROU
|
||||
(
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
USER_GROU_ID VARCHAR(10) NOT NULL,
|
||||
OGNZ_NO VARCHAR(7),
|
||||
USER_GROU_NM VARCHAR(100),
|
||||
USER_GROU_DS VARCHAR(1000),
|
||||
PUSE_YN VARCHAR(1),
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX S_EIAM_TIS.PK_AA_USER_GROU
|
||||
ON S_EIAM_TIS.AA_USER_GROU (SYST_ID,USER_GROU_ID);
|
||||
|
||||
ALTER TABLE S_EIAM_TIS.AA_USER_GROU
|
||||
ADD CONSTRAINT PK_AA_USER_GROU PRIMARY KEY (SYST_ID,USER_GROU_ID);
|
||||
|
||||
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.USER_GROU_ID IS '사용자그룹ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.OGNZ_NO IS '조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.USER_GROU_NM IS '사용자그룹명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.USER_GROU_DS IS '사용자그룹설명';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.PUSE_YN IS '사용여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
|
||||
-- 사용자/그룹관계
|
||||
CREATE TABLE S_EIAM_TIS.AA_USAC_USER_GROU_RTNS
|
||||
(
|
||||
USAC_USER_GROU_RTNS_ID VARCHAR(10) NOT NULL,
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
PRAF_NO VARCHAR(8) NOT NULL,
|
||||
USER_GROU_ID VARCHAR(10) NOT NULL,
|
||||
VLDT_YMD DATE,
|
||||
EXPR_YMD DATE,
|
||||
PUSE_YN VARCHAR(1),
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX S_EIAM_TIS.PK_AA_USAC_USER_GROU_RTNS
|
||||
ON S_EIAM_TIS.AA_USAC_USER_GROU_RTNS (USAC_USER_GROU_RTNS_ID);
|
||||
|
||||
ALTER TABLE S_EIAM_TIS.AA_USAC_USER_GROU_RTNS
|
||||
ADD CONSTRAINT PK_AA_USAC_USER_GROU_RTNS PRIMARY KEY (USAC_USER_GROU_RTNS_ID);
|
||||
|
||||
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.USAC_USER_GROU_RTNS_ID IS '사용자계정사용자그룹관계ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.PRAF_NO IS '인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.USER_GROU_ID IS '사용자그룹ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.VLDT_YMD IS '유효일자';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.EXPR_YMD IS '만료일자';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.PUSE_YN IS '사용여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USAC_USER_GROU_RTNS.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
|
||||
|
||||
-- 사용자그룹/역할 관계
|
||||
CREATE TABLE S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS
|
||||
(
|
||||
USER_GROU_ROLE_RTNS_ID VARCHAR(10) NOT NULL,
|
||||
SYST_ID VARCHAR(10) NOT NULL,
|
||||
USER_GROU_ID VARCHAR(10) NOT NULL,
|
||||
ROLE_NO VARCHAR(50) NOT NULL,
|
||||
PUSE_YN VARCHAR(1),
|
||||
SYST_RGI_DT DATE,
|
||||
SYST_RGI_PRAF_NO VARCHAR(8),
|
||||
SYST_RGI_OGNZ_NO VARCHAR(7),
|
||||
SYST_RGI_SYST_CD VARCHAR(3),
|
||||
SYST_RGI_PRGR_ID VARCHAR(100),
|
||||
SYST_CHG_DT DATE,
|
||||
SYST_CHG_PRAF_NO VARCHAR(8),
|
||||
SYST_CHG_OGNZ_NO VARCHAR(7),
|
||||
SYST_CHG_SYST_CD VARCHAR(3),
|
||||
SYST_CHG_PRGR_ID VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX S_EIAM_TIS.PK_AA_USER_GROU_ROLE_RTNS
|
||||
ON S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS (USER_GROU_ROLE_RTNS_ID);
|
||||
|
||||
ALTER TABLE S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS
|
||||
ADD CONSTRAINT PK_AA_USER_GROU_ROLE_RTNS PRIMARY KEY (USER_GROU_ROLE_RTNS_ID);
|
||||
|
||||
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.USER_GROU_ROLE_RTNS_ID IS '사용자그룹역할관계ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_ID IS '시스템ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.USER_GROU_ID IS '사용자그룹ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.ROLE_NO IS '역할번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.PUSE_YN IS '사용여부';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_RGI_DT IS '시스템등록일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_RGI_PRAF_NO IS '시스템등록인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_RGI_OGNZ_NO IS '시스템등록조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_RGI_SYST_CD IS '시스템등록시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_RGI_PRGR_ID IS '시스템등록프로그램ID';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_CHG_DT IS '시스템변경일시';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_CHG_PRAF_NO IS '시스템변경인사번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_CHG_OGNZ_NO IS '시스템변경조직번호';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_CHG_SYST_CD IS '시스템변경시스템코드';
|
||||
COMMENT ON COLUMN S_EIAM_TIS.AA_USER_GROU_ROLE_RTNS.SYST_CHG_PRGR_ID IS '시스템변경프로그램ID';
|
||||
|
||||
-- AA_OGNZ 테이블 생성 (조직)
|
||||
CREATE TABLE S_EIAM_TIS.AA_OGNZ (
|
||||
OGNZ_NM VARCHAR2(8) PRIMARY KEY, -- 인사번호 (PK)
|
||||
OGNZ_ABR_NM VARCHAR2(200), -- 인사명
|
||||
OGNZ_NO VARCHAR2(7) NOT NULL, -- 조직번호 (NOT NULL)
|
||||
SPPO_GONZ_NO VARCHAR2(250), -- 이메일주소
|
||||
VLDT_YMD VARCHAR2(10), -- 인사직무코드
|
||||
PUSE_YN VARCHAR2(100), -- 인사직무명
|
||||
SYST_RGI_DT DATE, -- 시스템등록일시
|
||||
SYST_RGI_PRAF_NO VARCHAR2(8), -- 시스템등록인사번호
|
||||
SYST_RGI_OGNZ_NO VARCHAR2(7), -- 시스템등록조직번호
|
||||
SYST_RGI_SYST_CD VARCHAR2(3), -- 시스템등록시스템코드
|
||||
SYST_RGI_PRGR_ID VARCHAR2(100), -- 시스템등록프로그램ID
|
||||
SYST_CHG_DT DATE, -- 시스템변경일시
|
||||
SYST_CHG_PRAF_NO VARCHAR2(8), -- 시스템변경인사번호
|
||||
SYST_CHG_OGNZ_NO VARCHAR2(7), -- 시스템변경조직번호
|
||||
SYST_CHG_SYST_CD VARCHAR2(3), -- 시스템변경시스템코드
|
||||
SYST_CHG_PRGR_ID VARCHAR2(100) -- 시스템변경프로그램ID
|
||||
);
|
||||
|
||||
|
||||
|
||||
8
dap-gateway/src/main/resources/spy.properties
Normal file
8
dap-gateway/src/main/resources/spy.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
# SLF4J ??? ?? (logback?? ???)
|
||||
appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||
|
||||
# ??? ??? ??? ?? (???? ?? ??)
|
||||
logMessageFormat=io.shinhanlife.dap.common.config.P6SpySqlFormatter
|
||||
|
||||
# ?? ?? 0ms?? (connection ?? ??) ???
|
||||
filter.executionThreshold=1
|
||||
805
dap-gateway/src/main/resources/static/admin/scaffold.html
Normal file
805
dap-gateway/src/main/resources/static/admin/scaffold.html
Normal file
@@ -0,0 +1,805 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko" class="dark" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AX HUB Developer Portal</title>
|
||||
<!-- Geist Fonts -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans@5.0.1/400.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans@5.0.1/500.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans@5.0.1/600.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/geist-mono@5.0.1/400.css">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #09090b;
|
||||
--surface-color: #18181b;
|
||||
--border-color: #3f3f46; /* zinc-700 */
|
||||
--text-primary: #f4f4f5; /* zinc-100 */
|
||||
--text-secondary: #a1a1aa; /* zinc-400 */
|
||||
--accent-color: #3b82f6;
|
||||
--accent-hover: #2563eb;
|
||||
--link-color: #60a5fa; /* blue-400 */
|
||||
--radius-md: 6px;
|
||||
--radius-sm: 4px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Geist Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px; /* Tighter layout */
|
||||
width: 95%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Editorial / Tech Header */
|
||||
.page-header {
|
||||
text-align: left;
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Clean Panel */
|
||||
.panel {
|
||||
background: var(--surface-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Minimalist Tabs */
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: #0f0f11;
|
||||
padding: 0 1.5rem;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-item {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: color 0.15s ease;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
color: var(--text-primary);
|
||||
border-bottom: 2px solid var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 2rem 1.5rem;
|
||||
background: var(--surface-color);
|
||||
}
|
||||
|
||||
/* Dense Forms */
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
background-color: var(--surface-color);
|
||||
color: var(--text-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
|
||||
background-color: var(--surface-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: var(--text-secondary);
|
||||
box-shadow: 0 0 0 1px var(--text-secondary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: #a1a1aa; /* zinc-400 */
|
||||
}
|
||||
|
||||
.input-hint {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
/* Utilitarian Button */
|
||||
.btn-action {
|
||||
background: var(--accent-color);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
transition: background-color 0.1s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
background: var(--accent-hover);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-action:active {
|
||||
transform: scale(0.98); /* Tactile feedback */
|
||||
}
|
||||
|
||||
.btn-secondary-action {
|
||||
background: var(--surface-color);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-secondary-action:hover {
|
||||
background: #f4f4f5;
|
||||
}
|
||||
|
||||
/* Result Box - Terminal Style */
|
||||
#resultBox {
|
||||
display: none;
|
||||
margin-top: 1.5rem;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1.25rem;
|
||||
white-space: pre-wrap;
|
||||
font-family: 'Geist Mono', Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.6;
|
||||
background-color: #09090b; /* zinc-950 */
|
||||
color: #e4e4e7; /* zinc-200 */
|
||||
border: 1px solid #27272a;
|
||||
}
|
||||
|
||||
#resultBox.success {
|
||||
border-top: 3px solid #10b981;
|
||||
}
|
||||
|
||||
#resultBox.error {
|
||||
border-top: 3px solid #ef4444;
|
||||
}
|
||||
|
||||
/* Dense Table */
|
||||
.table {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table th {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 0.75rem;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
padding: 1rem 0.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.badge-mono {
|
||||
font-family: 'Geist Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
padding: 0.25em 0.5em;
|
||||
border-radius: 4px;
|
||||
background: #f4f4f5;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
a.tool-link {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a.tool-link:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
/* Modal styling overrides - DARK THEME */
|
||||
.modal-content {
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8);
|
||||
background-color: #18181b;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1.25rem 1.5rem;
|
||||
background-color: #18181b;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
background-color: #18181b;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-body .form-label {
|
||||
color: #d4d4d8 !important;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 1rem 1.5rem;
|
||||
background-color: #18181b;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
/* Badge overrides for dark theme */
|
||||
.badge-mono {
|
||||
font-family: 'Geist Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
padding: 0.25em 0.5em;
|
||||
border-radius: 4px;
|
||||
background: #27272a;
|
||||
color: #a1a1aa;
|
||||
border: 1px solid #3f3f46;
|
||||
}
|
||||
|
||||
/* Bootstrap secondary button override */
|
||||
.btn-secondary-action:hover {
|
||||
background: #27272a;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Table hover for dark */
|
||||
.table > tbody > tr:hover > td {
|
||||
background-color: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
/* Tool name in table - ensure visibility */
|
||||
.table td {
|
||||
color: #e4e4e7 !important;
|
||||
}
|
||||
|
||||
.table td a.tool-link {
|
||||
color: #e4e4e7 !important;
|
||||
}
|
||||
|
||||
.table td a.tool-link:hover {
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
|
||||
/* Category badge in table */
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Alert/Notice dark theme */
|
||||
.alert-warning {
|
||||
background-color: rgba(245, 158, 11, 0.1);
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
/* Select option dark theme */
|
||||
select option, select optgroup {
|
||||
background-color: #18181b;
|
||||
color: #f4f4f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 72rem !important;
|
||||
padding-left: 1.5rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="overflow-y: scroll;">
|
||||
|
||||
<header style="border-bottom: 1px solid #27272a; background: rgba(9,9,11,0.85); backdrop-filter: blur(16px);" class="sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-5">
|
||||
<a href="/index.html" class="flex items-center group">
|
||||
<div class="w-2 h-2 rounded-full mr-2" style="background:#3b82f6; box-shadow: 0 0 8px rgba(59,130,246,0.8);"></div>
|
||||
<span class="font-semibold tracking-tight text-sm" style="color:#f4f4f5;">AXHUB Gateway</span>
|
||||
</a>
|
||||
<div class="h-4 w-px" style="background:#27272a;"></div>
|
||||
<nav class="flex space-x-5 text-[13px] font-medium">
|
||||
<a href="/admin/scaffold.html" style="color:#ffffff;" class="font-semibold">Scaffold</a>
|
||||
<a href="/catalog.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Catalog</a>
|
||||
<a href="/playground.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Playground</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[10px] uppercase tracking-widest px-2 py-1 rounded font-bold" style="background:rgba(59,130,246,0.1); color:#60a5fa; border:1px solid rgba(59,130,246,0.2);">v0.0.1</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>Developer Portal</h1>
|
||||
<p>AX HUB No-Code Scaffolding Wizard</p>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="pod-tab" data-bs-toggle="tab" data-bs-target="#pod" type="button" role="tab">Pod Module</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="tool-tab" data-bs-toggle="tab" data-bs-target="#tool" type="button" role="tab">Tool Function</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="list-tab" data-bs-toggle="tab" data-bs-target="#list" type="button" role="tab" onclick="loadToolList()">Registry</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="card-body p-0">
|
||||
<div class="tab-content" id="myTabContent">
|
||||
|
||||
<!-- Pod Creation Form -->
|
||||
<div class="tab-pane fade show active" id="pod" role="tabpanel">
|
||||
<form id="podForm">
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Module Name</label>
|
||||
<input type="text" class="form-control" name="moduleName" placeholder="e.g. hr (will be prefixed with axhub-tool-)" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Service Port</label>
|
||||
<input type="number" class="form-control" name="port" placeholder="e.g. 8086" required>
|
||||
<div class="input-hint">Unique port for Gateway routing and local development.</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Author</label>
|
||||
<input type="text" class="form-control" name="author" placeholder="Defaults to OS user">
|
||||
</div>
|
||||
<div class="col-md-6 mt-3 mt-md-0">
|
||||
<label class="form-label">Date</label>
|
||||
<input type="text" class="form-control" name="date" placeholder="Defaults to today">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button type="submit" class="btn-action">Generate Pod</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tool Creation Form -->
|
||||
<div class="tab-pane fade" id="tool" role="tabpanel">
|
||||
<form id="toolForm">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Base Name (PascalCase)</label>
|
||||
<input type="text" class="form-control" name="baseName" placeholder="e.g. ExchangeRate" required>
|
||||
<div class="input-hint">Used for class generation (ExchangeRateService).</div>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3 mt-md-0">
|
||||
<label class="form-label">Legacy Interface ID</label>
|
||||
<input type="text" class="form-control" name="interfaceId" placeholder="e.g. EXCH_001" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description (LLM Prompt)</label>
|
||||
<input type="text" class="form-control" name="description" placeholder="e.g. Fetch current exchange rates for customers." required>
|
||||
<div class="input-hint">Critical for AI agent intent matching. Be descriptive.</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Domain Category</label>
|
||||
<input type="text" class="form-control" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" list="groupList" placeholder="e.g. common, hr, payment" oninput="this.value = this.value.toLowerCase()" required>
|
||||
<datalist id="groupList">
|
||||
<option value="common">common</option>
|
||||
<option value="customer">customer</option>
|
||||
<option value="contract">contract</option>
|
||||
<option value="claim">claim</option>
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3 mt-md-0">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="routingType">
|
||||
<option value="HTTP">HTTP (REST)</option>
|
||||
<option value="TCP">TCP (Socket)</option>
|
||||
<option value="MCI">MCI (Legacy)</option>
|
||||
<option value="EAI">EAI (Internal)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Target Module</label>
|
||||
<select class="form-select" name="moduleName" id="targetModuleSelect" required>
|
||||
<option value="axhub-tool-other">axhub-tool-other</option>
|
||||
</select>
|
||||
<div class="input-hint">Destination Pod directory.</div>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3 mt-md-0">
|
||||
<label class="form-label">Gateway Registration</label>
|
||||
<select class="form-select" name="register">
|
||||
<option value="true">True (Public)</option>
|
||||
<option value="false">False (Private)</option>
|
||||
</select>
|
||||
<div class="input-hint">Controls dynamic routing exposure.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Author</label>
|
||||
<input type="text" class="form-control" name="author" placeholder="Defaults to OS user">
|
||||
</div>
|
||||
<div class="col-md-6 mt-3 mt-md-0">
|
||||
<label class="form-label">Date</label>
|
||||
<input type="text" class="form-control" name="date" placeholder="Defaults to today">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-4">
|
||||
<button type="submit" class="btn-action">Generate Tool</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tool List Form -->
|
||||
<div class="tab-pane fade" id="list" role="tabpanel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h5 class="mb-1" style="font-size: 1rem; font-weight: 600;">Active Tools</h5>
|
||||
<div class="input-hint mt-0">Tools dynamically registered in the Gateway.</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<select id="groupFilterSelect" class="form-select" style="width: auto; min-width: 140px; padding: 0.25rem 0.5rem; font-size: 0.8rem; height: 30px;" onchange="filterTools()">
|
||||
<option value="ALL">All Categories</option>
|
||||
</select>
|
||||
<input type="text" id="toolFilter" class="form-control" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; height: 30px;" placeholder="Search..." onkeyup="filterTools()">
|
||||
<button class="btn-secondary-action text-nowrap" style="height: 30px;" onclick="loadToolList()">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle" id="toolTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 10%;">Category</th>
|
||||
<th style="width: 20%;">Tool Name</th>
|
||||
<th style="width: 40%;">Description</th>
|
||||
<th style="width: 15%;">Pod</th>
|
||||
<th style="width: 15%;">Interface</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="toolListBody">
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-5 text-muted">Loading data...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resultBox"></div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Tool Modal -->
|
||||
<div class="modal fade" id="editToolModal" tabindex="-1" aria-labelledby="editToolModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editToolModalLabel">Edit Tool Definition</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editToolForm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tool Name</label>
|
||||
<input type="text" class="form-control" id="editToolName" name="toolName" readonly style="background-color: #f4f4f5; color: #a1a1aa;">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<input type="text" class="form-control" id="editCategoryKey" name="categoryKey" pattern="^[a-z0-9][a-z0-9_-]*$" oninput="this.value = this.value.toLowerCase()">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea class="form-control" id="editDescription" name="description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Registry Exposure</label>
|
||||
<select class="form-select" id="editRegister" name="register">
|
||||
<option value="true">True (Public)</option>
|
||||
<option value="false">False (Private)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #d97706; background: #fffbeb; padding: 0.75rem; border: 1px solid #fde68a; border-radius: var(--radius-sm); margin-top: 1rem;">
|
||||
<strong>Notice:</strong> Applying edits modifies the physical Java source code. A rebuild of the backend container is required for changes to take effect.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn-secondary-action" data-bs-dismiss="modal" style="padding: 0.5rem 1rem; font-size: 0.875rem;">Cancel</button>
|
||||
<button type="button" class="btn-action" onclick="submitToolUpdate()">Apply Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function handleFormSubmit(formId, apiUrl) {
|
||||
document.getElementById(formId).addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
const btn = this.querySelector('button[type="submit"]');
|
||||
const originalText = btn.innerHTML;
|
||||
|
||||
btn.innerHTML = 'Processing...';
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.7';
|
||||
|
||||
fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
const resultBox = document.getElementById('resultBox');
|
||||
resultBox.style.display = 'block';
|
||||
|
||||
if (result.includes('[오류]') || result.includes('오류 발생:')) {
|
||||
resultBox.className = 'error';
|
||||
resultBox.textContent = result;
|
||||
} else {
|
||||
resultBox.className = 'success';
|
||||
resultBox.textContent = result + "\n\n> Note: Please sync Gradle in your IDE to reflect source code generation.";
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
const resultBox = document.getElementById('resultBox');
|
||||
resultBox.style.display = 'block';
|
||||
resultBox.className = 'error';
|
||||
resultBox.textContent = "Network error: " + error;
|
||||
})
|
||||
.finally(() => {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
fetch('/api/v1/scaffold/modules')
|
||||
.then(res => res.json())
|
||||
.then(modules => {
|
||||
const select = document.getElementById('targetModuleSelect');
|
||||
if (modules && modules.length > 0) {
|
||||
select.innerHTML = modules.map(m => `<option value="${m}" ${m === 'axhub-tool-other' ? 'selected' : ''}>${m}</option>`).join('');
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('Failed to load modules:', err));
|
||||
});
|
||||
|
||||
handleFormSubmit('podForm', '/api/v1/scaffold/pod');
|
||||
handleFormSubmit('toolForm', '/api/v1/scaffold/tool');
|
||||
|
||||
function loadToolList() {
|
||||
const tbody = document.getElementById('toolListBody');
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-muted">Loading data...</td></tr>';
|
||||
|
||||
fetch('/mcp/api/v1/tools/list', {
|
||||
method: 'GET',
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
let tools = data.result.tools || [];
|
||||
if (tools.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-muted">No tools registered.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tools.sort((a, b) => {
|
||||
const gA = a.categoryKey || '';
|
||||
const gB = b.categoryKey || '';
|
||||
return gA.localeCompare(gB);
|
||||
});
|
||||
|
||||
const groupSet = new Set();
|
||||
tools.forEach(t => groupSet.add(t.categoryKey || 'unknown'));
|
||||
const groupSelect = document.getElementById('groupFilterSelect');
|
||||
const currentSelectedGroup = groupSelect.value;
|
||||
groupSelect.innerHTML = '<option value="ALL">All Categories</option>';
|
||||
Array.from(groupSet).sort().forEach(g => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = g;
|
||||
opt.textContent = g;
|
||||
groupSelect.appendChild(opt);
|
||||
});
|
||||
if (groupSet.has(currentSelectedGroup)) {
|
||||
groupSelect.value = currentSelectedGroup;
|
||||
}
|
||||
|
||||
tbody.innerHTML = tools.map(t => {
|
||||
const isReg = t.registered !== false;
|
||||
const badgeHtml = isReg ? '' : ' <span class="badge-mono" style="color:#d97706;border-color:#fde68a;background:#fffbeb;">Private</span>';
|
||||
const escDesc = (t.description || '').replace(/'/g, "\\'").replace(/"/g, '"');
|
||||
|
||||
return `
|
||||
<tr class="tool-row">
|
||||
<td><span class="badge-mono">${t.categoryKey || 'unknown'}</span></td>
|
||||
<td>
|
||||
<a href="javascript:void(0)" onclick="openEditModal('${t.name}', '${t.categoryKey}', '${escDesc}', ${isReg})" class="tool-link">
|
||||
${t.name}
|
||||
</a>
|
||||
${badgeHtml}
|
||||
</td>
|
||||
<td class="text-secondary" style="font-size:0.8rem;">${t.description}</td>
|
||||
<td><span class="badge-mono">${t.podUrl}</span></td>
|
||||
<td><span class="badge-mono">${t.integrationType}</span> <span class="text-secondary ms-1" style="font-size:0.75rem;">${t.mciServiceId || '-'}</span></td>
|
||||
</tr>
|
||||
`}).join('');
|
||||
|
||||
filterTools();
|
||||
})
|
||||
.catch(err => {
|
||||
tbody.innerHTML = `<tr><td colspan="5" class="text-center py-5 text-danger">Error loading data: ${err.message}</td></tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
function filterTools() {
|
||||
const input = document.getElementById('toolFilter').value.toLowerCase();
|
||||
const selectedGroup = document.getElementById('groupFilterSelect').value;
|
||||
const rows = document.querySelectorAll('.tool-row');
|
||||
|
||||
rows.forEach(row => {
|
||||
const name = row.cells[1].textContent.toLowerCase();
|
||||
const group = row.cells[0].textContent;
|
||||
|
||||
const matchText = name.includes(input) || group.toLowerCase().includes(input);
|
||||
const matchGroup = (selectedGroup === 'ALL' || group === selectedGroup);
|
||||
|
||||
if (matchText && matchGroup) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let editModalInstance = null;
|
||||
function openEditModal(toolName, categoryKey, description, isReg) {
|
||||
document.getElementById('editToolName').value = toolName;
|
||||
document.getElementById('editCategoryKey').value = categoryKey !== 'null' ? categoryKey : '';
|
||||
document.getElementById('editDescription').value = description;
|
||||
document.getElementById('editRegister').value = isReg ? 'true' : 'false';
|
||||
|
||||
if (!editModalInstance) {
|
||||
editModalInstance = new bootstrap.Modal(document.getElementById('editToolModal'));
|
||||
}
|
||||
editModalInstance.show();
|
||||
}
|
||||
|
||||
function submitToolUpdate() {
|
||||
const toolName = document.getElementById('editToolName').value;
|
||||
const categoryKey = document.getElementById('editCategoryKey').value;
|
||||
const description = document.getElementById('editDescription').value;
|
||||
const register = document.getElementById('editRegister').value;
|
||||
|
||||
const btn = document.querySelector('#editToolModal .btn-action');
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = 'Processing...';
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.7';
|
||||
|
||||
fetch('/api/v1/scaffold/tool/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
toolName: toolName,
|
||||
categoryKey: categoryKey,
|
||||
description: description,
|
||||
register: register
|
||||
})
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
if (result.includes('[오류]') || result.includes('오류 발생:')) {
|
||||
alert('Error updating source:\n' + result);
|
||||
} else {
|
||||
alert('Source code updated successfully.\nPlease rebuild the Pod container to reflect changes.');
|
||||
editModalInstance.hide();
|
||||
loadToolList();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert('Network error: ' + err);
|
||||
})
|
||||
.finally(() => {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
233
dap-gateway/src/main/resources/static/catalog.html
Normal file
233
dap-gateway/src/main/resources/static/catalog.html
Normal file
@@ -0,0 +1,233 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Tool Catalog</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { font-family: 'Geist', sans-serif; }
|
||||
body { background-color: #09090b; color: #f4f4f5; }
|
||||
.geist-mono { font-family: 'Geist Mono', monospace; }
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #3b82f6, #6366f1);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
.btn-primary:hover { box-shadow: 0 0 30px rgba(99, 102, 241, 0.5); transform: translateY(-1px); }
|
||||
|
||||
.tool-card {
|
||||
background: #18181b;
|
||||
border: 1px solid #3f3f46;
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.tool-card:hover { border-color: #52525b; }
|
||||
|
||||
.param-table {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.param-table th {
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
font-weight: 600;
|
||||
color: #a1a1aa;
|
||||
border-bottom: 1px solid #3f3f46;
|
||||
font-size: 12px;
|
||||
}
|
||||
.param-table td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #27272a;
|
||||
color: #d4d4d8;
|
||||
}
|
||||
.param-table tr:last-child td { border-bottom: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen" style="overflow-y: scroll;">
|
||||
|
||||
<header style="border-bottom: 1px solid #27272a; background: rgba(9,9,11,0.85); backdrop-filter: blur(16px);" class="sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-5">
|
||||
<a href="/index.html" class="flex items-center group">
|
||||
<div class="w-2 h-2 rounded-full mr-2" style="background:#3b82f6; box-shadow: 0 0 8px rgba(59,130,246,0.8);"></div>
|
||||
<span class="font-semibold tracking-tight text-sm" style="color:#f4f4f5;">AXHUB Gateway</span>
|
||||
</a>
|
||||
<div class="h-4 w-px" style="background:#27272a;"></div>
|
||||
<nav class="flex space-x-5 text-[13px] font-medium">
|
||||
<a href="/admin/scaffold.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Scaffold</a>
|
||||
<a href="/catalog.html" style="color:#ffffff;" class="font-semibold">Catalog</a>
|
||||
<a href="/playground.html" style="color:#a1a1aa;" class="hover:text-white transition-colors">Playground</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[10px] uppercase tracking-widest px-2 py-1 rounded font-bold" style="background:rgba(59,130,246,0.1); color:#60a5fa; border:1px solid rgba(59,130,246,0.2);">v0.0.1</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-6 py-10">
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-end mb-8">
|
||||
<div class="max-w-2xl">
|
||||
<h1 class="text-3xl font-bold tracking-tight mb-2" style="color:#f4f4f5;">Registry Catalog</h1>
|
||||
<p class="text-sm" style="color:#a1a1aa;">Currently integrated AI function tools across all modules. View parameters, descriptions, and integration types.</p>
|
||||
</div>
|
||||
<div class="mt-4 md:mt-0">
|
||||
<button id="copyMdBtn" class="btn-primary flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg>
|
||||
<span id="copyBtnText">Copy Markdown Docs</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="catalogContainer" class="space-y-12 pb-16">
|
||||
<div class="flex justify-center py-20 text-sm geist-mono" style="color:#71717a;">
|
||||
<div class="w-4 h-4 border-2 border-t-blue-500 rounded-full animate-spin mr-3" style="border-color:#3f3f46; border-top-color:#3b82f6;"></div>
|
||||
Loading registry...
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
async function fetchTools() {
|
||||
try {
|
||||
const response = await fetch('/mcp/api/v1/tools/list');
|
||||
const data = await response.json();
|
||||
const tools = data.result?.tools || [];
|
||||
renderCatalog(tools);
|
||||
} catch (err) {
|
||||
document.getElementById('catalogContainer').innerHTML = `
|
||||
<div style="padding:16px; border:1px solid #7f1d1d; background:rgba(127,29,29,0.15); color:#fca5a5; font-size:14px; border-radius:8px;">
|
||||
Failed to fetch tools from registry. Error: ${err.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderCatalog(tools) {
|
||||
const container = document.getElementById('catalogContainer');
|
||||
container.innerHTML = '';
|
||||
if (tools.length === 0) {
|
||||
container.innerHTML = '<div style="padding:32px; text-align:center; color:#71717a; border:1px dashed #3f3f46; border-radius:8px;">No tools registered.</div>';
|
||||
return;
|
||||
}
|
||||
const grouped = {};
|
||||
tools.forEach(tool => {
|
||||
const group = tool.categoryKey || 'Others';
|
||||
if (!grouped[group]) grouped[group] = [];
|
||||
grouped[group].push(tool);
|
||||
});
|
||||
Object.keys(grouped).sort().forEach(group => {
|
||||
const section = document.createElement('section');
|
||||
const header = document.createElement('div');
|
||||
header.style.cssText = 'margin-bottom:16px; padding-bottom:8px; border-bottom:1px solid #27272a; display:flex; align-items:baseline; justify-content:space-between;';
|
||||
header.innerHTML = `
|
||||
<h2 style="font-size:20px; font-weight:600; color:#f4f4f5; text-transform:capitalize;">${group}</h2>
|
||||
<span class="geist-mono" style="font-size:12px; color:#71717a;">${grouped[group].length} items</span>
|
||||
`;
|
||||
section.appendChild(header);
|
||||
const grid = document.createElement('div');
|
||||
grid.style.cssText = 'display:grid; grid-template-columns:1fr; gap:12px;';
|
||||
grouped[group].forEach(tool => grid.appendChild(createToolCard(tool)));
|
||||
section.appendChild(grid);
|
||||
container.appendChild(section);
|
||||
});
|
||||
}
|
||||
|
||||
function createToolCard(tool) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'tool-card';
|
||||
|
||||
const isDirect = tool.integrationType === 'DIRECT';
|
||||
const badgeBg = isDirect ? 'rgba(59,130,246,0.15)' : 'rgba(249,115,22,0.15)';
|
||||
const badgeColor = isDirect ? '#60a5fa' : '#fb923c';
|
||||
const badgeBorder = isDirect ? 'rgba(59,130,246,0.3)' : 'rgba(249,115,22,0.3)';
|
||||
|
||||
let paramHtml = '';
|
||||
if (tool.parametersSchema && tool.parametersSchema.properties) {
|
||||
const props = tool.parametersSchema.properties;
|
||||
const required = tool.parametersSchema.required || [];
|
||||
if (Object.keys(props).length > 0) {
|
||||
paramHtml = `
|
||||
<div style="margin-top:16px; padding-top:16px; border-top:1px solid #27272a;">
|
||||
<div style="font-size:10px; font-weight:700; color:#71717a; margin-bottom:8px; letter-spacing:0.1em; text-transform:uppercase;">Parameters</div>
|
||||
<table class="param-table">
|
||||
<thead><tr><th style="width:25%;">Name</th><th style="width:15%;">Type</th><th>Description</th></tr></thead>
|
||||
<tbody>
|
||||
${Object.entries(props).map(([k,v]) => `
|
||||
<tr>
|
||||
<td class="geist-mono" style="color:#93c5fd;">${k}${required.includes(k) ? '<span style="color:#f87171; margin-left:4px;">*</span>' : ''}</td>
|
||||
<td class="geist-mono" style="color:#a1a1aa;">${v.type || 'string'}</td>
|
||||
<td style="color:#d4d4d8;">${v.description || '-'}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
let promptHtml = '';
|
||||
if (tool.actionPrompts && Object.keys(tool.actionPrompts).length > 0) {
|
||||
promptHtml = `
|
||||
<div style="margin-top:16px; padding-top:16px; border-top:1px solid #27272a;">
|
||||
<div style="font-size:10px; font-weight:700; color:#71717a; margin-bottom:8px; letter-spacing:0.1em; text-transform:uppercase;">Action Prompts</div>
|
||||
<ul style="list-style:disc; padding-left:20px; font-size:14px; color:#a1a1aa; space-y:2;">
|
||||
${Object.values(tool.actionPrompts).map(p => `<li style="margin-bottom:4px;">${p}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
card.innerHTML = `
|
||||
<div style="display:flex; align-items:flex-start; justify-content:space-between;">
|
||||
<div style="flex:1;">
|
||||
<div style="display:flex; align-items:center; margin-bottom:4px;">
|
||||
<h3 style="font-size:16px; font-weight:600; color:#f4f4f5;">${tool.displayName || tool.name}</h3>
|
||||
<span style="margin:0 8px; color:#3f3f46;">/</span>
|
||||
<span class="geist-mono" style="font-size:12px; color:#71717a;">${tool.name}</span>
|
||||
</div>
|
||||
<p style="font-size:14px; color:#a1a1aa;">${tool.description || 'No description provided.'}</p>
|
||||
</div>
|
||||
<div style="margin-left:16px; padding:4px 10px; border-radius:6px; font-size:11px; font-weight:600; background:${badgeBg}; color:${badgeColor}; border:1px solid ${badgeBorder};" class="geist-mono">
|
||||
${tool.integrationType || 'DIRECT'}
|
||||
</div>
|
||||
</div>
|
||||
${paramHtml}
|
||||
${promptHtml}
|
||||
`;
|
||||
return card;
|
||||
}
|
||||
|
||||
document.getElementById('copyMdBtn').addEventListener('click', async (e) => {
|
||||
const btn = e.currentTarget;
|
||||
const textSpan = document.getElementById('copyBtnText');
|
||||
const originalText = textSpan.textContent;
|
||||
try {
|
||||
const res = await fetch('/mcp/api/v1/tools/docs/markdown');
|
||||
if(!res.ok) throw new Error('Failed to fetch markdown');
|
||||
const mdText = await res.text();
|
||||
await navigator.clipboard.writeText(mdText);
|
||||
textSpan.textContent = 'Copied!';
|
||||
setTimeout(() => { textSpan.textContent = originalText; }, 2000);
|
||||
} catch (err) {
|
||||
alert('Failed to copy markdown.');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', fetchTools);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
235
dap-gateway/src/main/resources/static/chat.html
Normal file
235
dap-gateway/src/main/resources/static/chat.html
Normal file
@@ -0,0 +1,235 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AX HUB - ChatClient</title>
|
||||
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; background-color: #0f1115; color: #e2e8f0; }
|
||||
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
||||
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
|
||||
.message-enter { animation: fadeInUp 0.3s ease-out forwards; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.typing-dot { animation: typing 1.4s infinite ease-in-out; fill: #94a3b8; }
|
||||
.typing-dot:nth-child(1) { animation-delay: 0s; }
|
||||
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
@keyframes typing {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-3px); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-screen flex flex-col items-center justify-center p-4">
|
||||
|
||||
<!-- Chat Container -->
|
||||
<div class="w-full max-w-3xl h-[85vh] flex flex-col bg-[#16181d] rounded-2xl shadow-2xl overflow-hidden border border-white/5 relative">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="px-6 py-4 border-b border-white/5 flex items-center justify-between bg-[#16181d] z-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-full bg-emerald-500/20 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-sm font-semibold tracking-wide text-white">AX HUB Assistant</h1>
|
||||
<p class="text-[11px] text-slate-500">Powered by Mock Engine & MCP Tools</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="relative flex h-2.5 w-2.5">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
|
||||
</span>
|
||||
<span class="text-xs text-slate-400">Online</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Area -->
|
||||
<div id="chat-box" class="flex-1 overflow-y-auto p-6 flex flex-col gap-6 scrollbar-hide scroll-smooth">
|
||||
|
||||
<!-- System Greeting -->
|
||||
<div class="flex gap-4 message-enter">
|
||||
<div class="w-8 h-8 rounded-full bg-emerald-500/10 flex-shrink-0 flex items-center justify-center mt-1 border border-emerald-500/20">
|
||||
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 max-w-[80%]">
|
||||
<span class="text-xs text-slate-500 ml-1 font-medium">Assistant</span>
|
||||
<div class="bg-[#1e2128] px-5 py-3.5 rounded-2xl rounded-tl-sm text-sm text-slate-200 leading-relaxed border border-white/5 shadow-sm">
|
||||
안녕하세요! 저는 AX HUB Assistant입니다. <br>
|
||||
채팅을 통해 MCP(도구)들을 직접 호출해보세요. <br><br>
|
||||
💡 <strong>예시:</strong> "오늘 서울 날씨 어때?"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Input Area -->
|
||||
<div class="p-4 bg-[#16181d] border-t border-white/5 relative">
|
||||
<div class="relative flex items-center w-full max-w-full mx-auto group">
|
||||
<input type="text" id="chat-input" placeholder="메시지를 입력하세요... (예: 서울 날씨 어때?)"
|
||||
class="w-full bg-[#1e2128] text-slate-200 text-sm px-5 py-4 rounded-xl border border-white/5 focus:outline-none focus:border-emerald-500/50 focus:ring-1 focus:ring-emerald-500/50 transition-all placeholder-slate-600 pr-12 shadow-inner"
|
||||
onkeypress="handleKeyPress(event)">
|
||||
<button onclick="sendMessage()" class="absolute right-2 p-2 rounded-lg bg-emerald-500 hover:bg-emerald-400 text-white transition-colors flex items-center justify-center shadow-lg shadow-emerald-500/20">
|
||||
<svg class="w-4 h-4 translate-x-[1px]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 로딩 인디케이터 템플릿 -->
|
||||
<template id="loading-template">
|
||||
<div class="flex gap-4 message-enter" id="loading-indicator">
|
||||
<div class="w-8 h-8 rounded-full bg-emerald-500/10 flex-shrink-0 flex items-center justify-center mt-1 border border-emerald-500/20">
|
||||
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 max-w-[80%]">
|
||||
<span class="text-xs text-slate-500 ml-1 font-medium">Assistant</span>
|
||||
<div class="bg-[#1e2128] px-5 py-4 rounded-2xl rounded-tl-sm border border-white/5 flex items-center gap-1 shadow-sm h-[48px]">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle class="typing-dot" cx="4" cy="12" r="3"/>
|
||||
<circle class="typing-dot" cx="12" cy="12" r="3"/>
|
||||
<circle class="typing-dot" cx="20" cy="12" r="3"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const chatBox = document.getElementById('chat-box');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const loadingTemplate = document.getElementById('loading-template');
|
||||
let isLoading = false;
|
||||
|
||||
function handleKeyPress(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function appendUserMessage(text) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex gap-4 justify-end message-enter';
|
||||
div.innerHTML = `
|
||||
<div class="flex flex-col gap-1 max-w-[80%] items-end">
|
||||
<span class="text-xs text-slate-500 mr-1 font-medium">You</span>
|
||||
<div class="bg-emerald-500/10 px-5 py-3.5 rounded-2xl rounded-tr-sm text-sm text-emerald-50 leading-relaxed border border-emerald-500/20 shadow-sm whitespace-pre-wrap">${escapeHtml(text)}</div>
|
||||
</div>
|
||||
`;
|
||||
chatBox.appendChild(div);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function appendBotMessageContainer() {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex gap-4 message-enter';
|
||||
div.innerHTML = `
|
||||
<div class="w-8 h-8 rounded-full bg-emerald-500/10 flex-shrink-0 flex items-center justify-center mt-1 border border-emerald-500/20">
|
||||
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 max-w-[80%]">
|
||||
<span class="text-xs text-slate-500 ml-1 font-medium">Assistant</span>
|
||||
<div class="bg-[#1e2128] px-5 py-3.5 rounded-2xl rounded-tl-sm text-sm text-slate-200 leading-relaxed border border-white/5 shadow-sm whitespace-pre-wrap bot-text"></div>
|
||||
</div>
|
||||
`;
|
||||
chatBox.appendChild(div);
|
||||
scrollToBottom();
|
||||
return div.querySelector('.bot-text');
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
const clone = loadingTemplate.content.cloneNode(true);
|
||||
chatBox.appendChild(clone);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
const loadingIndicator = document.getElementById('loading-indicator');
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
chatBox.scrollTo({ top: chatBox.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const text = chatInput.value.trim();
|
||||
if (!text || isLoading) return;
|
||||
|
||||
// 1. 유저 메시지 추가
|
||||
appendUserMessage(text);
|
||||
chatInput.value = '';
|
||||
isLoading = true;
|
||||
|
||||
// 2. 로딩 애니메이션
|
||||
showLoading();
|
||||
|
||||
try {
|
||||
// 3. 백엔드 통신 (SSE Stream)
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Agent-Id': 'TESTER-DEV' // 권한 통과를 위한 테스트 Agent ID
|
||||
},
|
||||
body: JSON.stringify({ message: text })
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
|
||||
// 4. 로딩 숨기고 봇 응답 컨테이너 생성
|
||||
hideLoading();
|
||||
const textContainer = appendBotMessageContainer();
|
||||
|
||||
// 5. 스트림 읽기
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
const lines = chunk.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
const data = line.substring(5);
|
||||
textContainer.innerHTML += escapeHtml(data);
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
hideLoading();
|
||||
const textContainer = appendBotMessageContainer();
|
||||
textContainer.innerHTML = "서버와의 통신에 실패했습니다. Gateway가 켜져있는지 확인해주세요.";
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
169
dap-gateway/src/main/resources/static/index.html
Normal file
169
dap-gateway/src/main/resources/static/index.html
Normal file
@@ -0,0 +1,169 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AXHUB Gateway</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Geist', 'sans-serif'],
|
||||
mono: ['Geist Mono', 'monospace']
|
||||
},
|
||||
colors: {
|
||||
background: '#09090b', /* zinc-950 */
|
||||
panel: '#18181b', /* zinc-900 */
|
||||
foreground: '#f4f4f5', /* zinc-100 */
|
||||
muted: '#a1a1aa', /* zinc-400 */
|
||||
border: 'rgba(255,255,255,0.1)',
|
||||
primary: '#3b82f6', /* blue-500 */
|
||||
primaryHover: '#2563eb', /* blue-600 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: theme('colors.background');
|
||||
color: theme('colors.foreground');
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Subtle radial background for depth */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: radial-gradient(circle at 50% -20%, rgba(59, 130, 246, 0.15), transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.geist-mono { font-family: 'Geist Mono', monospace; }
|
||||
|
||||
.card-panel {
|
||||
background: rgba(24, 24, 27, 0.7); /* zinc-900 with transparency */
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid theme('colors.border');
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.card-panel:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 20px -8px rgba(0,0,0,0.6), 0 0 15px rgba(59, 130, 246, 0.15), inset 0 1px 0 rgba(255,255,255,0.1);
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
background: rgba(39, 39, 42, 0.8); /* zinc-800 */
|
||||
}
|
||||
|
||||
.premium-text-gradient {
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-image: linear-gradient(135deg, #ffffff 0%, #a1a1aa 100%);
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card-panel:hover .icon-box {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
border-color: rgba(59, 130, 246, 0.4);
|
||||
color: #60a5fa; /* blue-400 */
|
||||
box-shadow: 0 0 15px rgba(59, 130, 246, 0.3), inset 0 1px 0 rgba(255,255,255,0.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen flex flex-col relative">
|
||||
|
||||
<!-- Header Navigation -->
|
||||
<header class="border-b border-border sticky top-0 bg-background/80 backdrop-blur-xl z-50">
|
||||
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-5">
|
||||
<div class="flex items-center">
|
||||
<div class="w-2 h-2 rounded-full bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.8)] mr-2"></div>
|
||||
<span class="font-semibold tracking-tight text-sm text-zinc-100">AXHUB Gateway</span>
|
||||
</div>
|
||||
<div class="h-4 w-px bg-white/10"></div>
|
||||
<nav class="flex space-x-5 text-[13px] font-medium">
|
||||
<a href="/admin/scaffold.html" class="text-zinc-400 hover:text-white transition-colors">Scaffold</a>
|
||||
<a href="/catalog.html" class="text-zinc-400 hover:text-white transition-colors">Catalog</a>
|
||||
<a href="/playground.html" class="text-zinc-400 hover:text-white transition-colors">Playground</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[10px] uppercase tracking-widest bg-blue-500/10 text-blue-400 px-2 py-1 rounded font-bold border border-blue-500/20">v0.0.1</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-grow max-w-6xl mx-auto px-6 py-20 w-full flex flex-col items-center justify-center relative z-10">
|
||||
|
||||
<div class="text-center mb-20 max-w-2xl">
|
||||
<div class="inline-flex items-center justify-center px-3 py-1 mb-6 text-xs font-medium rounded-full bg-white/5 border border-white/10 shadow-sm backdrop-blur-md">
|
||||
<span class="flex w-2 h-2 rounded-full bg-blue-400 shadow-[0_0_5px_rgba(96,165,250,0.8)] mr-2 animate-pulse"></span>
|
||||
<span class="text-zinc-300">System Operational</span>
|
||||
</div>
|
||||
<h1 class="text-5xl font-bold tracking-tight mb-6 premium-text-gradient leading-tight">AI Plugin Gateway</h1>
|
||||
<p class="text-[15px] text-zinc-400 leading-relaxed max-w-lg mx-auto">
|
||||
The central hub for managing, discovering, and testing AI tool integrations. High-performance tooling for the modern enterprise.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-5xl">
|
||||
<!-- Scaffold Card -->
|
||||
<a href="/admin/scaffold.html" class="card-panel p-8 group block">
|
||||
<div class="w-12 h-12 icon-box rounded-xl flex items-center justify-center mb-6 text-zinc-300">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-zinc-100 mb-3">Scaffold Tool</h2>
|
||||
<p class="text-[13px] text-zinc-400 leading-relaxed">
|
||||
Generate boilerplate code for new AI tool integrations automatically. Select a target module and build your plugin structure in seconds.
|
||||
</p>
|
||||
<div class="mt-6 flex items-center text-[12px] font-semibold text-zinc-300 group-hover:text-blue-400 group-hover:translate-x-1 transition-all">
|
||||
Launch Scaffold <svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Catalog Card -->
|
||||
<a href="/catalog.html" class="card-panel p-8 group block">
|
||||
<div class="w-12 h-12 icon-box rounded-xl flex items-center justify-center mb-6 text-zinc-300">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path></svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-zinc-100 mb-3">Tool Catalog</h2>
|
||||
<p class="text-[13px] text-zinc-400 leading-relaxed">
|
||||
Browse all active tool integrations. View JSON schemas, parameters, and action prompts registered across the entire AXHUB ecosystem.
|
||||
</p>
|
||||
<div class="mt-6 flex items-center text-[12px] font-semibold text-zinc-300 group-hover:text-blue-400 group-hover:translate-x-1 transition-all">
|
||||
View Catalog <svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Playground Card -->
|
||||
<a href="/playground.html" class="card-panel p-8 group block">
|
||||
<div class="w-12 h-12 icon-box rounded-xl flex items-center justify-center mb-6 text-zinc-300">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-zinc-100 mb-3">Playground</h2>
|
||||
<p class="text-[13px] text-zinc-400 leading-relaxed">
|
||||
Test and execute tools directly from the browser. Verify RPC calls, test inputs, and inspect raw JSON responses before using them in code.
|
||||
</p>
|
||||
<div class="mt-6 flex items-center text-[12px] font-semibold text-zinc-300 group-hover:text-blue-400 group-hover:translate-x-1 transition-all">
|
||||
Enter Playground <svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user