fix(sync): Ignore Broken Pipe on SSE emitter in warm pool and optimize SyncRunner
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled

This commit is contained in:
jade
2026-08-14 16:27:09 +09:00
parent d8a8501c4e
commit 1d69bd914e
17 changed files with 78 additions and 641 deletions

View File

@@ -223,9 +223,19 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor
// this.emitter.complete(); // MCP 표준 클라이언트 지원을 위해 스트림 강제 종료 제거
}
} catch (Exception e) {
log.error("Error sending message to SSE emitter", e);
if (e.getClass().getSimpleName().contains("AsyncRequestNotUsableException") ||
(e.getCause() != null && e.getCause() instanceof java.io.IOException)) {
log.debug("SSE emitter is disconnected (expected in Warm Pool): {}", e.getMessage());
} else {
log.error("Error sending message to SSE emitter", e);
}
if (this.emitter != null) {
this.emitter.completeWithError(e);
try {
this.emitter.completeWithError(e);
} catch (Exception ignored) {
// ignore already completed
}
}
}
});

View File

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

View File

@@ -1,19 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.insurance.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 InsuranceClaimProcessorHttpRequest {
@Schema(description = "보험 청구 번호", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimNumber;
@Schema(description = "청구 금액", example = "1500000.00", requiredMode = Schema.RequiredMode.REQUIRED)
private Double claimAmount;
@Schema(description = "청구 일자 (YYYY-MM-DD)", example = "2023-09-15", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimDate;
}

View File

@@ -1,16 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.insurance.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 InsuranceClaimProcessorHttpResponse {
private String resultCode;
private String resultMessage;
@Schema(description = "청구 처리 고유 식별자", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimId;
}

View File

@@ -1,72 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
import io.shinhanlife.glow.communication.dto.Transfer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io
* @className MciCfpaClient
* @description 보장분석결과조회 MCI 호출 클라이언트
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MciCfpaClient {
private final AxhubMciComponent mci;
// 인터페이스 코드 상수
private static final String INTERFACE_CODE = "CLCNNB00001"; // CLCCFP00001 (Onnba용 코드 유지)
// 정상 응답 코드 상수
private static final String SUCCESS_CODE_COM = "COM00139";
private static final String SUCCESS_CODE_CFP = "CFP00000";
private static final String NO_DATA_CODE = "COM00150";
// 예외 코드 상수
private static final String ERROR_CODE_PROCESS = "CLC00007";
private static final String ERROR_CODE_MESSAGE = "CLC00043";
/**
* 보장분석결과조회
*
* @param inDto 입력 DTO
* @return 출력 객체
*/
public Object callCfpa0001(CLCNNB00001_I mciReq) throws Exception {
// 인터페이스 IO 객체 생성 및 매핑
// 인터페이스 호출
Transfer<CLCNNB00001_O> resTransfer = mci.callTo(INTERFACE_CODE, mciReq, CLCNNB00001_O.class);
// 응답 검증
validateResponse(resTransfer);
// 메시지 검증
validateMessage(String.valueOf(resTransfer.getMessage()));
return resTransfer.getBody();
}
private void validateResponse(Transfer<?> resTransfer) {
log.info("응답 검증 로직 수행");
}
private void validateMessage(String message) {
log.info("메시지 검증 로직 수행: {}", message);
}
}

View File

@@ -1,91 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io
* @className CLCNNB00001_I
* @description 보장분석결과조회 MCI 요청 전문
* @author 0986406
* @create 2026.09.01
*/
@Data
public class CLCNNB00001_I {
@JsonProperty("unfcPrbuIrcoAddu")
private UnfcPrbuIrcoAdduDto unfcPrbuIrcoAddu;
@JsonProperty("dalScCd")
private String dalScCd;
@JsonProperty("cstSucoRltyCd")
private String cstSucoRltyCd;
@JsonProperty("csNo")
private String csNo;
@JsonProperty("rdreNo")
private String rdreNo;
@JsonProperty("unfcPvsCalReqYn")
private String unfcPvsCalReqYn;
@JsonProperty("kcisPymmTnnrRequest")
private String kcisPymmTnnrRequest;
@JsonProperty("lmovYn")
private String lmovYn;
@JsonProperty("genPsthApvTrgtYn")
private String genPsthApvTrgtYn;
@JsonProperty("ircoLmovEcpbTrgtYn")
private String ircoLmovEcpbTrgtYn;
@JsonProperty("digCalYn")
private String digCalYn;
@JsonProperty("prbuIciDigCalYn")
private String prbuIciDigCalYn;
@JsonProperty("sucoIspaBasDto")
private SucoIspaBasDto sucoIspaBasDto;
@Data
public static class UnfcPrbuIrcoAdduDto {
}
@Data
public static class SucoIspaBasDto {
@JsonProperty("ccnDalTypCd")
private String ccnDalTypCd;
@JsonProperty("nwcnptCursCd")
private String nwcnptCursCd;
@JsonProperty("induAsctScCd")
private String induAsctScCd;
@JsonProperty("cepeOgnzNo")
private String cepeOgnzNo;
@JsonProperty("cepePrafNo")
private String cepePrafNo;
@JsonProperty("clmoOgnzNo")
private String clmoOgnzNo;
@JsonProperty("clmoPrafNo")
private String clmoPrafNo;
@JsonProperty("sucoYmd")
private String sucoYmd;
@JsonProperty("ispDt")
private String ispDt;
@JsonProperty("contYmd")
private String contYmd;
}
}

View File

@@ -1,16 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io
* @className CLCNNB00001_O
* @description 보장분석결과조회 MCI 응답 전문
* @author 0986406
* @create 2026.09.01
*/
@Data
public class CLCNNB00001_O {
// 응답 전문 필드 정의 (필요에 따라 추가)
private Object resultData;
}

View File

@@ -1,30 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g;
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.ncl.g
* @className MciNclgClient
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Component
@RequiredArgsConstructor
public class MciNclgClient {
private final AxhubMciComponent mci;
public <T> Transfer<T> callTo(String interfaceId, String dummy, Object mciReq, Class<T> resType) throws Exception {
return mci.callTo(interfaceId, dummy, mciReq, resType);
}
}

View File

@@ -1,24 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
* @className SOLG00000001_I
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
public class SOLG00000001_I {
private String reqStatus;
private String reqPeriod;
private String reqTarget;
}

View File

@@ -1,34 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
import lombok.Data;
import java.util.List;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
* @className SOLG00000001_O
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
public class SOLG00000001_O {
private List<SOLG00000001_O_Item> reqList;
@Data
public static class SOLG00000001_O_Item {
private String srId;
private String srName;
private String process;
private String devStage;
private String appName;
private String requester;
}
}

View File

@@ -1,23 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
* @className SOLG00000002_I
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
public class SOLG00000002_I {
private String srId;
}

View File

@@ -1,31 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
* @className SOLG00000002_O
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
public class SOLG00000002_O {
private String srId;
private String srName;
private String process;
private String devStage;
private String appName;
private String requester;
private String requestDate;
private String dueDate;
private String description;
}

View File

@@ -1,68 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncm.d.io;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* ONCMD0030_O 매핑 DTO
*/
@NoArgsConstructor
@Getter
@Setter
public class ONCMD0030_O {
@GlowTrgmField(order = 1, description = "고객스마트정보조회OutDto", type = "gm")
private List<CstSmartIfinOutDto> cstSmartIfinOutDto;
@GlowTrgmField(order = 1, description = "고객스마트정보조회OutDto2", type = "gm")
private List<CstSmartIfinOutDto2> cstSmartIfinOutDto2;
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
@Getter
@Setter
public static class CstSmartIfinOutDto {
@GlowTrgmField(order = 1, length = 1, description = "동의여부")
private String agrYn;
@GlowTrgmField(order = 2, length = 12, description = "고객번호")
private String csNo;
@GlowTrgmField(order = 3, length = 50, description = "주민등록번호")
private String rdreNo;
@GlowTrgmField(order = 4, length = 20, description = "등록일시")
private String rgiDt;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
@Getter
@Setter
public static class CstSmartIfinOutDto2 {
@GlowTrgmField(order = 1, length = 1, description = "동의여부")
private String agrYnaa;
@GlowTrgmField(order = 2, length = 12, description = "고객번호")
private String csNoaa;
@GlowTrgmField(order = 3, length = 50, description = "주민등록번호")
private String rdreNoaa;
@GlowTrgmField(order = 4, length = 20, description = "등록일시")
private String rgiDtaa;
}
}

View File

@@ -1,48 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_O;
import io.shinhanlife.glow.BizException;
import io.shinhanlife.glow.GlowLogger;
import lombok.extern.slf4j.Slf4j;
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.nild
* @className MciNildClient
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author Boram
* @create 2026.08.12
* <pre>
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* 2026.08.12 Boram 理쒖큹?앹꽦
*
* </pre>
*/
@Component
@RequiredArgsConstructor
public class MciNildClient {
private final AxhubMciComponent mci;
public ONILD0320_O callOnild0320(ONILD0320_I onild0320_i){
try {
Transfer<ONILD0320_O> resTransfer = mci.callTo(
"CTMNILO00007",
null,
onild0320_i,
ONILD0320_O.class
);
ONILD0320_O onild0320_o = resTransfer.getBody();
return onild0320_o;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,41 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.shinhanlife.glow.GlowMciFieldInfo;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Data
@Getter
@Setter
@NoArgsConstructor
public class ONILD0320_I {
@GlowTrgmField(order = 1, description = "고객 통합 안내이력 조회", type="gs")
private CstUnfcNotiCarhInqrInDto cstUnfcNotiCarhInqrInDto;
@NoArgsConstructor
@Getter
@Setter
public static class CstUnfcNotiCarhInqrInDto {
@GlowMciFieldInfo(order = 1, length = 5, description = "고객번호")
private String csNo;
@GlowMciFieldInfo(order = 2, length = 5, description = "안내장코드")
private String ntleCd;
@GlowMciFieldInfo(order = 3, length = 5, description = "안내발송방법코드")
private String notiPmlMdCd;
@GlowMciFieldInfo(order = 4, length = 5, description = "조회시작일자")
private String inqrStrtYmd;
@GlowMciFieldInfo(order = 5, length = 5, description = "조회종료일자")
private String inqrEndYmd;
}
}

View File

@@ -1,109 +0,0 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.shinhanlife.glow.GlowMciFieldInfo;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.List;
@NoArgsConstructor
@Getter
@Setter
public class ONILD0320_O {
@GlowTrgmField(order = 1, description = "고객 통합 안내이력 조회out", type="gm")
private List<CstUnfcNotiCarhInqrOutDto> cstUnfcNotiCarhInqrOutDto;
@NoArgsConstructor
@Getter
@Setter
public static class CstUnfcNotiCarhInqrOutDto {
@GlowMciFieldInfo(order = 1, length = 5, description = "고객번호")
private String csNo;
@GlowMciFieldInfo(order = 2, length = 5, description = "안내일시")
private String notiDt;
@GlowMciFieldInfo(order = 3, length = 5, description = "발송일련번호")
private int pmlSriaNo;
@GlowMciFieldInfo(order = 4, length = 5, description = "안내신청상세일시")
private String notiPetDtptDt;
@GlowMciFieldInfo(order = 5, length = 5, description = "안내장코드")
private String ntleCd;
@GlowMciFieldInfo(order = 6, length = 5, description = "안내장명")
private String ntleNm;
@GlowMciFieldInfo(order = 7, length = 5, description = "안내발송일련번호")
private int notiPmlSriaNo;
@GlowMciFieldInfo(order = 8, length = 5, description = "안내발송방법코드")
private String notiPmlMdCd;
@GlowMciFieldInfo(order = 9, length = 5, description = "안내수신정보암호화내용")
private String notiRcvInfoEncrCt;
@GlowMciFieldInfo(order = 10, length = 5, description = "메시지ID")
private String msgId;
@GlowMciFieldInfo(order = 11, length = 5, description = "안내내용")
private String notiCt;
@GlowMciFieldInfo(order = 12, length = 5, description = "통합안내발송결과코드")
private String unfcNotiPmlRsltCd;
@GlowMciFieldInfo(order = 13, length = 5, description = "통합안내발송결과상세코드")
private String unfcNotiPmlRsltDtptCd;
@GlowMciFieldInfo(order = 14, length = 5, description = "발송시스템코드")
private String pmlSystCd;
@GlowMciFieldInfo(order = 15, length = 5, description = "발송조직번호")
private String pmlOgnzNo;
@GlowMciFieldInfo(order = 16, length = 5, description = "발송조직명")
private String pmlOgnzNm;
@GlowMciFieldInfo(order = 17, length = 5, description = "발신기관조직명")
private String msdilttOgnzNm;
@GlowMciFieldInfo(order = 18, length = 5, description = "조회구분명")
private String inqrDvsnNm;
@GlowMciFieldInfo(order = 19, length = 5, description = "등록일시")
private String rgiDt;
@GlowMciFieldInfo(order = 20, length = 5, description = "등록자명")
private String rgstNm;
@GlowMciFieldInfo(order = 21, length = 5, description = "등록자조직명")
private String rgstOgnzNm;
@GlowMciFieldInfo(order = 22, length = 5, description = "최종변경자명")
private String lstPrwpNm;
@GlowMciFieldInfo(order = 23, length = 5, description = "최종변경일시")
private String lstChgDt;
@GlowMciFieldInfo(order = 24, length = 5, description = "최종변경자조직명")
private String lstPrwpOgnzNm;
@GlowMciFieldInfo(order = 25, length = 5, description = "원천회사구분코드")
private String oriCpnyDvsnCd;
@GlowMciFieldInfo(order = 26, length = 5, description = "고객한글명")
private String cstHanNm;
@GlowMciFieldInfo(order = 27, length = 5, description = "시스템등록인사번호")
private String systRgiPrafNo;
@GlowMciFieldInfo(order = 28, length = 5, description = "시스템변경인사번호")
private String systChgPrafNo;
}
}

View File

@@ -0,0 +1,66 @@
package io.shinhanlife.dap.lib.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.shinhanlife.dap.lib.metadata.ToolDefinition;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import java.util.List;
/**
* CI/CD 및 로컬 개발 환경에서 V17 Tool YAML 정의 파일을 읽어 Java @McpTool 어노테이션을 동기화합니다.
*/
public final class ToolAnnotationSyncRunner {
private ToolAnnotationSyncRunner() {
}
public static void main(String[] args) {
if (args.length != 1) {
throw new IllegalArgumentException("Usage: ToolAnnotationSyncRunner <project-root>");
}
sync(Path.of(args[0]));
}
static void sync(Path projectRoot) {
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
int syncCount = 0;
try (Stream<Path> files = Files.walk(projectRoot)) {
List<Path> yamlFiles = files.filter(Files::isRegularFile)
.filter(path -> path.toString().replace('\\', '/').contains("/src/main/resources/tool-definitions/"))
.filter(path -> path.toString().endsWith(".yml") || path.toString().endsWith(".yaml"))
.toList();
for (Path file : yamlFiles) {
ToolDefinition definition = yamlMapper.readValue(file.toFile(), ToolDefinition.class);
String toolName = definition.name();
String title = definition.displayName();
String description = definition.displayDescription();
boolean readOnlyHint = Boolean.TRUE.equals(definition.readOnly());
boolean destructiveHint = Boolean.TRUE.equals(definition.destructive());
boolean idempotentHint = Boolean.TRUE.equals(definition.idempotent());
String categoryKey = definition.categoryKey();
try {
boolean updated = ToolSourceUpdater.syncToolSource(projectRoot, toolName, title, description,
readOnlyHint, destructiveHint, idempotentHint, categoryKey);
if (updated) {
syncCount++;
System.out.println("Synced (Updated): " + toolName);
} else {
System.out.println("Synced (Unchanged): " + toolName);
}
} catch (IllegalArgumentException e) {
System.err.println("Skipped (Not Found): " + toolName);
}
}
} catch (IOException exception) {
throw new IllegalStateException("Failed to run tool annotation sync", exception);
}
System.out.println("Tool annotation sync completed: " + syncCount + " tools updated.");
}
}