feat: MCP SSE 통신 개선, 호스트 바인딩 및 사용자 신규 비즈니스 모듈 코드 추가
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 3m42s

This commit is contained in:
Gitea CI
2026-08-12 14:35:42 +09:00
parent 2c4c447a8f
commit a6b62803ab
271 changed files with 15471 additions and 2 deletions

View File

@@ -0,0 +1,14 @@
package io.shinhanlife.dap.mcc.biz.cmm.converter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_O;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface ClaimSearchConverter {
CLCNNB00001_I toLegacyRequest(ClaimSearchRequest request);
ClaimSearchRequest toRequest(CLCNNB00001_I mciRequest);
ClaimSearchResponse toResponse(CLCNNB00001_O mciRes);
}

View File

@@ -0,0 +1,17 @@
package io.shinhanlife.dap.mcc.biz.cmm.converter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse;
import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpRequest;
import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MemoListRetrieverConverter {
// Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField")
MemoListRetrieverHttpRequest toHttpRequest(MemoListRetrieverRequest request);
MemoListRetrieverResponse toResponse(MemoListRetrieverHttpResponse httpResponse);
}

View File

@@ -0,0 +1,16 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ClaimSearchRequest {
@Schema(description = "보험금 청구번호", example = "CLM202608100001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimNo;
@Schema(description = "보험 계약번호", example = "10023456789")
private String contractNo;
}

View File

@@ -0,0 +1,22 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ClaimSearchResponse {
private String resultCode;
private String resultMessage;
@Schema(description = "청구 처리 상태 코드", example = "RECEIVED", requiredMode = Schema.RequiredMode.REQUIRED)
private String status;
@Schema(description = "청구 처리 상태명", example = "접수", requiredMode = Schema.RequiredMode.REQUIRED)
private String statusLabel;
@Schema(description = "승인 금액", example = "150000")
private Long approvedAmount;
}

View File

@@ -0,0 +1,16 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MemoListRetrieverRequest {
@Schema(description = "조회할 의뢰서 상태", example = "OPEN", requiredMode = Schema.RequiredMode.REQUIRED)
private String memoStatus;
@Schema(description = "검색 키워드", example = "프로젝트", requiredMode = Schema.RequiredMode.REQUIRED)
private String searchKeyword;
}

View File

@@ -0,0 +1,13 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MemoListRetrieverResponse {
private String resultCode;
private String resultMessage;
}

View File

@@ -0,0 +1,29 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.ToolHint;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
* @className ClaimSearchUseCase
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.08.10
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.08.10 jade 최초생성
*
* </pre>
*/
public interface ClaimSearchUseCase {
@McpTool(name = "cmm_claim_search", title = "청구번호 또는 계약번호로 보험금 청구 상태를 조회한다.", description = "청구번호 또는 계약번호로 보험금 청구 상태를 조회한다.")
@ToolHint(register = false, categoryKey = "cmm", mappingId = "CLCNNB00001",
inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json",
outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json")
ClaimSearchResponse execute(ClaimSearchRequest req);
}

View File

@@ -0,0 +1,27 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.ToolHint;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
* @className MemoListRetrieverUseCase
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author Admin
* @create 2026.08.11
* <pre>
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* 2026.08.11 Admin 理쒖큹?앹꽦
*
* </pre>
*/
public interface MemoListRetrieverUseCase {
@McpTool(name = "cmm_memo_retriever", title = "의뢰서 목록 조회", description = "의뢰서 목록을 조회하여 의뢰 정보를 반환합니다.")
@ToolHint(register = false, categoryKey = "cmm", mappingId = "MEMO0000001")
MemoListRetrieverResponse execute(MemoListRetrieverRequest req);
}

View File

@@ -0,0 +1,68 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchUseCase;
import io.shinhanlife.glow.communication.dto.Transfer;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import io.shinhanlife.dap.mcc.biz.cmm.converter.ClaimSearchConverter;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_O;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.MciNclaClient;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl
* @className ClaimSearchUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.08.10
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.08.10 jade 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ClaimSearchUseCaseImpl implements ClaimSearchUseCase {
private final MciNclaClient mci;
private final ClaimSearchConverter converter;
@Override
public ClaimSearchResponse execute(ClaimSearchRequest req) {
log.info("[MCI Tool] {} 요청 수신.", "cmm_claim_search");
try {
// MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO)
CLCNNB00001_I mciReq = converter.toLegacyRequest(req);
Transfer<CLCNNB00001_O> resTransfer = mci.callTo(
"CLCNNB00001",
null,
mciReq,
CLCNNB00001_O.class
);
ClaimSearchResponse response = new ClaimSearchResponse();
if (resTransfer.getBody() != null) {
response = converter.toResponse(resTransfer.getBody());
}
response.setResultCode("SUCCESS");
response.setResultMessage(resTransfer.getBody() != null
? "MCI call completed."
: "MCI call completed without a response body.");
return response;
} catch (Exception e) {
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
ClaimSearchResponse response = new ClaimSearchResponse();
response.setResultCode("ERROR");
response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error");
return response;
}
}
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.converter.MemoListRetrieverConverter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse;
import io.shinhanlife.dap.mcc.infra.itrf.http.memo.MemoClient;
import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpRequest;
import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpResponse;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.MemoListRetrieverUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MemoListRetrieverUseCaseImpl implements MemoListRetrieverUseCase {
private final MemoListRetrieverConverter converter;
private final MemoClient memoClient;
@Override
public MemoListRetrieverResponse execute(MemoListRetrieverRequest req) {
MemoListRetrieverHttpRequest httpRequest = converter.toHttpRequest(req);
MemoListRetrieverHttpResponse httpResponse = memoClient.call(httpRequest, MemoListRetrieverHttpResponse.class);
MemoListRetrieverResponse response = converter.toResponse(httpResponse);
response.setResultCode("SUCCESS");
response.setResultMessage("HTTP API call completed.");
return response;
}
}

View File

@@ -0,0 +1,17 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.memo;
import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class MemoClient {
private static final String API_NAME = "memo";
private final AxhubHttpComponent http;
public <I, O> O call(I request, Class<O> responseType) {
return http.call(API_NAME, request, responseType);
}
}

View File

@@ -0,0 +1,16 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.memo.io;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MemoListRetrieverHttpRequest {
@Schema(description = "조회할 의뢰서 상태", example = "OPEN", requiredMode = Schema.RequiredMode.REQUIRED)
private String memoStatus;
@Schema(description = "검색 키워드", example = "프로젝트", requiredMode = Schema.RequiredMode.REQUIRED)
private String searchKeyword;
}

View File

@@ -0,0 +1,13 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.memo.io;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MemoListRetrieverHttpResponse {
private String resultCode;
private String resultMessage;
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
import io.shinhanlife.glow.communication.dto.Transfer;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla
* @className MciNclaClient
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.08.10
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.08.10 jade 최초생성
*
* </pre>
*/
@Component
@RequiredArgsConstructor
public class MciNclaClient {
private final AxhubMciComponent mci;
public <O> Transfer<O> callTo(String interfaceId, String dummy, Object mciReq, Class<O> resType) throws Exception {
return mci.callTo(interfaceId, dummy, mciReq, resType);
}
}

View File

@@ -0,0 +1,14 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class CLCNNB00001_I {
@Schema(description = "보험금 청구번호", example = "CLM202608100001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimNo;
@Schema(description = "보험 계약번호", example = "10023456789")
private String contractNo;
}

View File

@@ -0,0 +1,17 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class CLCNNB00001_O {
@Schema(description = "청구 처리 상태 코드", example = "RECEIVED", requiredMode = Schema.RequiredMode.REQUIRED)
private String status;
@Schema(description = "청구 처리 상태명", example = "접수", requiredMode = Schema.RequiredMode.REQUIRED)
private String statusLabel;
@Schema(description = "승인 금액", example = "150000")
private Long approvedAmount;
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcc.sms;
/**
* @package io.shinhanlife.dap.mcc.sms
* @className DapWasSmsApplication
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
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.mcc", "io.shinhanlife.dap.lib"})
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"})
@EnableCaching
public class DapWasSmsApplication {
public static void main(String[] args) {
SpringApplication.run(DapWasSmsApplication.class, args);
}
}

View File

@@ -0,0 +1,15 @@
# OCI ?대씪?곕뱶 ?섍꼍 ?꾩슜 ?ㅼ젙
server:
port: ${PORT:8082}
axhub:
gateway:
url: https://axhubmcp.devjun.net
spring:
config:
activate:
on-profile: dev
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-dev.yml

View File

@@ -0,0 +1,28 @@
# Local 환경 전용 설정 (H2 메모리 DB 등)
spring:
config:
activate:
on-profile: local
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-local.yml
datasource:
url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
driverClassName: com.p6spy.engine.spy.P6SpyDriver
username: sa
password: password
h2:
console:
enabled: true
mcp:
security:
tenant-domains:
mcp-client-1: CUSTOMER,COMMON
mcp-client-2: ALL
axhub:
gateway:
url: http://localhost:8081
tool:
url: ${AXHUB_TOOL_URL:http://localhost:${server.port}}

View File

@@ -0,0 +1,16 @@
server:
port: ${PORT:8082}
spring:
config:
activate:
on-profile: prod
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-prod.yml
axhub:
gateway:
url: ${AXHUB_GATEWAY_URL}
tool:
url: ${AXHUB_TOOL_URL}

View File

@@ -0,0 +1,16 @@
server:
port: ${PORT:8082}
spring:
config:
activate:
on-profile: test
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-test.yml
axhub:
gateway:
url: ${AXHUB_GATEWAY_URL}
tool:
url: ${AXHUB_TOOL_URL}

View File

@@ -0,0 +1,19 @@
server:
port: 8082
spring:
application:
name: dap-was-sms
profiles:
active: local
logging:
level:
org.apache.kafka: ERROR
mcp:
namespace: ""
manifest:
bundle-id: tool-sms
# Set the AA-assigned prefix before MCP pull activation (for example: sms.).
name-prefix: ""
security:
tenant-domains:
TESTER-DEV: ALL

View File

@@ -0,0 +1,39 @@
<?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) 출력 설정 (서버 운영용) -->
<!-- Logback에서 시스템 Hostname을 가져오기 위한 설정 -->
<property name="HOSTNAME" value="${HOSTNAME}" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/swlog/dap-was-sms/A01/${HOSTNAME}_A01.log</file> <!-- 현재 로그가 쌓이는 파일 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 매일 자정에 날짜를 붙여서 지난 로그를 분리 저장 -->
<fileNamePattern>/swlog/dap-was-sms/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.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>

View File

@@ -0,0 +1,3 @@
{
"resultCode": "SUCCESS"
}

View File

@@ -0,0 +1,5 @@
{
"status" : "RECEIVED",
"statusLabel" : "접수",
"approvedAmount" : 150000
}

View File

@@ -0,0 +1,11 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"TODO_FIELD": {
"type": "string",
"description": "TODO: 파라미터 설명을 입력하세요."
}
},
"required": []
}

View File

@@ -0,0 +1,16 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"status": {
"type": "string",
"description": "처리 결과 상태 (SUCCESS / FAILURE)",
"enum": ["SUCCESS", "FAILURE"]
},
"message": {
"type": "string",
"description": "처리 결과 메시지"
}
},
"required": ["status"]
}