forked from kimhyungsik/ax_hub_mcp_tool
style: remove all emojis from source code as per user rule
This commit is contained in:
@@ -3,11 +3,11 @@
|
||||
이 파일은 AI Agent(Antigravity)가 이 프로젝트에서 작업할 때 항상 명심하고 지켜야 할 규칙을 정의하는 파일입니다.
|
||||
작업 시 유의해야 할 사항이 생기면 언제든지 이 아래에 자유롭게 내용을 추가해 주세요!
|
||||
|
||||
## 💡 개발 가이드라인
|
||||
## 개발 가이드라인
|
||||
* 패키지명은 `controller` 대신 `presentation`을 사용합니다.
|
||||
* MapStruct 사용 시, 테스트 환경 에러를 방지하기 위해 생성자(`new ...Impl()`) 대신 `Mappers.getMapper(인터페이스명.class)` 방식으로 인스턴스를 가져옵니다.
|
||||
|
||||
## 📌 명심해야 할 규칙 추가란
|
||||
## 명심해야 할 규칙 추가란
|
||||
* 이모지는 무조건 넣지 않는다
|
||||
* import 할것 무조건 한다
|
||||
* 자바 만들때는 무조건 아래 내용을 넣는다
|
||||
|
||||
26
README.md
26
README.md
@@ -4,7 +4,7 @@ Spring Boot 기반 AXHUB 관리자 백엔드 API 서버 및 MCP(Model Context Pr
|
||||
|
||||
---
|
||||
|
||||
## 🚀 아키텍처 개요 (Architecture Overview)
|
||||
## 아키텍처 개요 (Architecture Overview)
|
||||
|
||||
AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
|
||||
@@ -14,7 +14,7 @@ AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
|
||||
---
|
||||
|
||||
## 🛠 환경 (Environment)
|
||||
## 환경 (Environment)
|
||||
|
||||
| 항목 | 버전 |
|
||||
|------|------|
|
||||
@@ -29,7 +29,7 @@ AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
|
||||
---
|
||||
|
||||
## ▶️ 실행 방법 (How to Run)
|
||||
## ▶ 실행 방법 (How to Run)
|
||||
|
||||
### 1. Gateway & Tool 서버 실행 (MCP 연동용)
|
||||
- **Gateway 서버 기동:**
|
||||
@@ -46,7 +46,7 @@ AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
- `HR`: 휴가 등록, 연차 갯수 조회
|
||||
- `CONTRACT`: 계약 상태, 계약 상세 조회
|
||||
- `CUSTOMER`: 고객 등급, 고객 상세 정보 조회
|
||||
- IntelliJ IDEA: `Run/Debug Configurations` ➔ `AxHubToolApplication` ➔ `Program arguments` 에 `--mcp.tool.target=NOTIFICATION` 입력
|
||||
- IntelliJ IDEA: `Run/Debug Configurations` `AxHubToolApplication` `Program arguments` 에 `--mcp.tool.target=NOTIFICATION` 입력
|
||||
|
||||
### 2. Admin 관리자 서버 실행
|
||||
- **Admin 서버 기동:**
|
||||
@@ -54,7 +54,7 @@ AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Gemini MCP 연동 (Integration)
|
||||
## Gemini MCP 연동 (Integration)
|
||||
|
||||
본 시스템은 REST API(JSON-RPC)를 사용하므로, **표준 MCP 통신(Stdio)**을 지원하기 위한 브릿지 스크립트를 내장하고 있습니다. 이를 통해 제미나이(Gemini Code Assist, Gemini CLI 등)와 코드 수정 없이 완벽히 연동됩니다.
|
||||
|
||||
@@ -73,7 +73,7 @@ AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
|
||||
---
|
||||
|
||||
## 🔒 비공개 Tool 관리 및 Fallback 연동 (Visibility & Routing)
|
||||
## 비공개 Tool 관리 및 Fallback 연동 (Visibility & Routing)
|
||||
|
||||
저희 시스템은 MSA 보안 및 아키텍처 원칙에 따라 Tool의 **레지스트리 등록 여부(라우팅)**와 **API 노출 여부(가시성)**를 완벽히 분리하여 관리합니다.
|
||||
|
||||
@@ -93,7 +93,7 @@ AXHUB Backend는 3개의 주요 애플리케이션으로 분리 운영됩니다:
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 안정성 및 트래픽 제어 (Resilience4j)
|
||||
## 안정성 및 트래픽 제어 (Resilience4j)
|
||||
|
||||
MSA(Microservices Architecture) 환경의 안정성을 위해 완벽한 2-Track 방어막을 구축했습니다.
|
||||
1. **Gateway 계층 (동적 방어):** Tool이 등록할 때 제출한 메타데이터(SLA)를 기반으로 Gateway 내에서 동적 CircuitBreaker 및 RateLimiter를 가동합니다. 한계치 초과 시 트래픽을 Kafka 큐로 비동기 전환합니다.
|
||||
@@ -101,11 +101,11 @@ MSA(Microservices Architecture) 환경의 안정성을 위해 완벽한 2-Track
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 모듈(Pod) 및 Tool 코드 자동 생성 (Scaffolders)
|
||||
## 모듈(Pod) 및 Tool 코드 자동 생성 (Scaffolders)
|
||||
|
||||
새로운 도메인의 기능을 추가할 때 발생하는 반복적인 설정(보일러플레이트, 설정 파일 복사 등)을 1초 만에 자동화하기 위해 **AXHUB Developer Portal (Web UI)** 및 **CLI 스캐폴더 2종**을 제공합니다.
|
||||
|
||||
### 🌟 1. AXHUB Developer Portal (Web UI) - 가장 추천하는 방식!
|
||||
### 1. AXHUB Developer Portal (Web UI) - 가장 추천하는 방식!
|
||||
이제 더 이상 터미널에서 명령어를 칠 필요가 없습니다. Gateway 모듈에 내장된 웹 화면에서 빈칸만 채우면 코드가 마법처럼 찍혀 나옵니다.
|
||||
|
||||
1. **접속 방법**: Gateway 서버 기동 후 브라우저에서 `http://localhost:8081/admin/scaffold.html` 접속
|
||||
@@ -115,7 +115,7 @@ MSA(Microservices Architecture) 환경의 안정성을 위해 완벽한 2-Track
|
||||
### 2. CLI 스캐폴더 (기존 터미널 방식)
|
||||
웹 화면을 사용할 수 없는 환경이거나 터미널이 익숙한 경우, 아래 명령어를 통해 CLI 마법사를 사용할 수 있습니다.
|
||||
|
||||
### 1️⃣ 새로운 Pod(모듈) 전체를 생성할 때: `PodScaffolder`
|
||||
### 1⃣ 새로운 Pod(모듈) 전체를 생성할 때: `PodScaffolder`
|
||||
새로운 도메인(예: 결제, HR)을 위한 완전히 독립적인 Spring Boot 모듈을 생성합니다. 폴더 구조, 빌드 스크립트, 각종 프로퍼티 및 도커 설정까지 완벽하게 세팅됩니다.
|
||||
|
||||
```bash
|
||||
@@ -127,7 +127,7 @@ java -cp axhub-common/src/main/java io.shinhanlife.axhub.common.util.PodScaffold
|
||||
java -cp axhub-common/src/main/java io.shinhanlife.axhub.common.util.PodScaffolder hr 8086
|
||||
```
|
||||
|
||||
### 2️⃣ 생성된 모듈에 새로운 툴(Function)을 추가할 때: `ToolScaffolder`
|
||||
### 2⃣ 생성된 모듈에 새로운 툴(Function)을 추가할 때: `ToolScaffolder`
|
||||
어노테이션(`@McpTool`, `@McpFunction`)이 완벽히 달린 Service와 입출력 DTO 코드를 지정된 모듈 패키지 룰에 맞춰 자동 생성합니다.
|
||||
|
||||
```bash
|
||||
@@ -141,11 +141,11 @@ java -cp axhub-common/src/main/java io.shinhanlife.axhub.common.util.ToolScaffol
|
||||
|
||||
---
|
||||
|
||||
## 📂 패키지 구조 (Package Structure)
|
||||
## 패키지 구조 (Package Structure)
|
||||
|
||||
```text
|
||||
axhub-backend-main (Root)
|
||||
├── axhub-gateway # 💡 MCP 라우팅 허브 서버 (외부 LLM과 통신 및 Tool 분배)
|
||||
├── axhub-gateway # MCP 라우팅 허브 서버 (외부 LLM과 통신 및 Tool 분배)
|
||||
├── axhub-common # 공통 모듈 (Security, Session, Config 등)
|
||||
├── axhub-tool-core # Tool 공통 기능 (AbstractMcpToolService, Annotation, Scaffolder)
|
||||
├── axhub-tool-email # [Tool] 이메일 발송 특화 어댑터 모듈
|
||||
|
||||
@@ -45,7 +45,7 @@ public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<JsonRpcResponse> handleAllException(Exception e) {
|
||||
log.error("💥 [Gateway Fatal Error] 치명적 오류 발생", e);
|
||||
log.error(" [Gateway Fatal Error] 치명적 오류 발생", e);
|
||||
return buildErrorResponse(-32000, "Server error: 시스템 관리자에게 문의하세요.");
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ public class PodScaffolder {
|
||||
}
|
||||
|
||||
log.append("\n=========================================\n");
|
||||
log.append(" 🎉 Pod Scaffolding Complete! \n");
|
||||
log.append(" Pod Scaffolding Complete! \n");
|
||||
log.append("=========================================\n");
|
||||
log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n");
|
||||
log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n");
|
||||
|
||||
@@ -36,10 +36,10 @@ public class GlowMciParserTest {
|
||||
|
||||
// then
|
||||
System.out.println("==================================================");
|
||||
System.out.println("✅ [원본 MCI 전문] : [" + rawMciString + "]");
|
||||
System.out.println("✅ [파싱된 ID (10자리)] : [" + result.getCustomerId() + "]");
|
||||
System.out.println("✅ [파싱된 Name (15자리)] : [" + result.getName() + "]");
|
||||
System.out.println("✅ [파싱된 Age (3자리)] : [" + result.getAge() + "]");
|
||||
System.out.println(" [원본 MCI 전문] : [" + rawMciString + "]");
|
||||
System.out.println(" [파싱된 ID (10자리)] : [" + result.getCustomerId() + "]");
|
||||
System.out.println(" [파싱된 Name (15자리)] : [" + result.getName() + "]");
|
||||
System.out.println(" [파싱된 Age (3자리)] : [" + result.getAge() + "]");
|
||||
System.out.println("==================================================");
|
||||
|
||||
assertNotNull(result);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class KafkaProducerService {
|
||||
String jsonPayload = jsonMapper.writeValueAsString(payload);
|
||||
// 실제 Kafka 서버로 전송
|
||||
kafkaTemplate.send(topic, ticketId, jsonPayload);
|
||||
log.info("🎫 [Kafka Queue] 요청 대기열 등록 완료 - Topic: {}, Ticket ID: {}", topic, ticketId);
|
||||
log.info(" [Kafka Queue] 요청 대기열 등록 완료 - Topic: {}, Ticket ID: {}", topic, ticketId);
|
||||
} catch (Exception e) {
|
||||
// 로컬 테스트 시 실제 Kafka 브로커가 없어서 나는 예외를 방어합니다.
|
||||
// 실제 상용 환경에서는 Dead Letter Queue 등을 태우거나 예외 처리 정책에 따릅니다.
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ToolPlanner {
|
||||
* 요청(payload)을 분석하여 실행해야 할 Tool의 계획을 생성합니다.
|
||||
*/
|
||||
public Object createPlan(Map<String, Object> payload, String tenantId) {
|
||||
log.info("📝 [Planner] 요청 분석 및 실행 계획 수립 시작");
|
||||
log.info(" [Planner] 요청 분석 및 실행 계획 수립 시작");
|
||||
|
||||
// 1. 요청에서 호출하려는 툴 이름 추출 (JSON-RPC params.name)
|
||||
Map<String, Object> params = (Map<String, Object>) payload.get("params");
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ExternalApiConnector {
|
||||
Object payload = isFixedLength ? payloadBuilder.buildFixedLengthString(spec, data) : data;
|
||||
String contentType = isFixedLength ? "application/x-www-form-urlencoded;charset=EUC-KR" : "application/json";
|
||||
|
||||
log.info("🌐 외부 API 호출 [{}] 시작 (FixedLength: {})", apiName, isFixedLength);
|
||||
log.info(" 외부 API 호출 [{}] 시작 (FixedLength: {})", apiName, isFixedLength);
|
||||
|
||||
return restClient.post()
|
||||
.uri(endpoint)
|
||||
|
||||
@@ -37,7 +37,7 @@ public class LegacyDbConnector {
|
||||
@RateLimiter(name = "legacyDb", fallbackMethod = "fallbackForDb")
|
||||
@CircuitBreaker(name = "legacyDb", fallbackMethod = "fallbackForDb")
|
||||
public List<Map<String, Object>> executeDynamicQuery(String queryId, String sql, Map<String, Object> params) {
|
||||
log.info("🗄 Legacy DB 조회 시작 [QueryID: {}]", queryId);
|
||||
log.info(" Legacy DB 조회 시작 [QueryID: {}]", queryId);
|
||||
|
||||
// 1. SQL 쿼리 실행 (오라클 등)
|
||||
List<Map<String, Object>> rawResults = jdbcTemplate.queryForList(sql, params);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ThirdPartySecurityConnector {
|
||||
* 3rd Party 보안 모듈(DRM, BM 등)과 연동하기 위한 전용 메서드입니다.
|
||||
*/
|
||||
public String executeSecurityModule(String interfaceId, Map<String, Object> data) throws Exception {
|
||||
log.info("🔐 [3rd Party Security] 보안 모듈 연동 시작 - Interface: {}", interfaceId);
|
||||
log.info(" [3rd Party Security] 보안 모듈 연동 시작 - Interface: {}", interfaceId);
|
||||
|
||||
// 보안 모듈 통신을 위한 특수 페이로드 조립 (예시)
|
||||
// 실제로는 RestClient나 WebClient를 통해 보안 VM의 전용 엔드포인트로 호출합니다.
|
||||
@@ -49,7 +49,7 @@ public class ThirdPartySecurityConnector {
|
||||
"data", data != null ? data : Map.of()
|
||||
));
|
||||
} else if (interfaceId.startsWith("BM_")) {
|
||||
log.info("👆 [BM 처리] 바이오 인증 모듈과 통신 중...");
|
||||
log.info(" [BM 처리] 바이오 인증 모듈과 통신 중...");
|
||||
resultJson = jsonMapper.writeValueAsString(Map.of(
|
||||
"status", "SUCCESS",
|
||||
"module", "Bio-Metric",
|
||||
|
||||
@@ -35,7 +35,7 @@ public class EaiEimsSender implements EimsSender {
|
||||
// 실전 코드: 스프링이 제공하는 카프카 템플릿 주입
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
|
||||
// 🎫 비동기 티켓 매니저
|
||||
// 비동기 티켓 매니저
|
||||
private final TicketManager ticketManager;
|
||||
|
||||
@Override
|
||||
@@ -47,12 +47,12 @@ public class EaiEimsSender implements EimsSender {
|
||||
String xmlData = xmlMapper.writeValueAsString(jsonNode);
|
||||
String esbStandardXml = wrapWithEaiHeader(interfaceId, xmlData);
|
||||
|
||||
log.info("📦 [EAI 어댑터] Kafka 토픽(eai-topic)으로 전송 시도...");
|
||||
log.info(" [EAI 어댑터] Kafka 토픽(eai-topic)으로 전송 시도...");
|
||||
|
||||
try {
|
||||
// 실전 코드 적용: Kafka로 메시지 발행
|
||||
kafkaTemplate.send("eai-topic", esbStandardXml);
|
||||
log.info("📦 [EAI 어댑터] Kafka 전송 완료!");
|
||||
log.info(" [EAI 어댑터] Kafka 전송 완료!");
|
||||
} catch (Exception e) {
|
||||
// 로컬 환경에는 카프카가 없으므로 에러가 날 수 있습니다. 테스트를 위해 로깅만 하고 넘깁니다.
|
||||
log.warn(" 로컬 환경이거나 Ka<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
|
||||
@@ -82,7 +82,7 @@ public class EaiEimsSender implements EimsSender {
|
||||
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
log.info("📊 [SLA 모니터링 - EAI] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
log.info(" [SLA 모니터링 - EAI] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class HttpEimsSender implements EimsSender {
|
||||
|
||||
@Override
|
||||
public String send(String interfaceId, String payload) {
|
||||
log.info("🌐 [HTTP 모드] EIMS API 호출 중... URL: {}", eimsUrl);
|
||||
log.info(" [HTTP 모드] EIMS API 호출 중... URL: {}", eimsUrl);
|
||||
|
||||
// EIMS가 요구하는 JSON 포맷으로 래핑해서 전송 (EIMS 규격에 따라 수정 가능)
|
||||
Map<String, String> requestBody = Map.of(
|
||||
@@ -52,7 +52,7 @@ public class HttpEimsSender implements EimsSender {
|
||||
"data", payload
|
||||
);
|
||||
|
||||
// 📊 4번 항목 적용: MDC에 저장된 traceId를 추출하여 HTTP Header(X-Trace-Id)로 전파
|
||||
// 4번 항목 적용: MDC에 저장된 traceId를 추출하여 HTTP Header(X-Trace-Id)로 전파
|
||||
String traceId = MDC.get("traceId");
|
||||
if (traceId == null) traceId = "SYSTEM-GENERATED-" + UUID.randomUUID().toString();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class JspFormEimsSender implements EimsSender {
|
||||
|
||||
@Override
|
||||
public String send(String interfaceId, String payload) {
|
||||
log.info("🌐 [JSP Form 모드] 레거시 폼 데이터 전송 중... URL: {}", jspUrl);
|
||||
log.info(" [JSP Form 모드] 레거시 폼 데이터 전송 중... URL: {}", jspUrl);
|
||||
|
||||
// 1. Form Data 조립 (HTML <form> 태그 전송과 동일한 효과)
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
|
||||
@@ -37,7 +37,7 @@ public class JspJsonEimsSender implements EimsSender {
|
||||
|
||||
@Override
|
||||
public String send(String interfaceId, String payload) {
|
||||
log.info("🌐 [JSP JSON 모드] JSON 페이로드 전송 중... URL: {}", jspUrl);
|
||||
log.info(" [JSP JSON 모드] JSON 페이로드 전송 중... URL: {}", jspUrl);
|
||||
|
||||
// 1. JSON 객체로 조립 (스프링이 알아서 JSON String으로 변환해 줌)
|
||||
Map<String, String> jsonBody = Map.of(
|
||||
|
||||
@@ -49,7 +49,7 @@ public class MciEimsSender implements EimsSender {
|
||||
String xmlData = xmlMapper.writer().withRootName("Body").writeValueAsString(jsonNode);
|
||||
String esbStandardXml = wrapWithEsbHeader(interfaceId, xmlData);
|
||||
|
||||
log.info("🌐 [ESB 어댑터] 전송 준비 완료 - RestClient 호출 시작");
|
||||
log.info(" [ESB 어댑터] 전송 준비 완료 - RestClient 호출 시작");
|
||||
|
||||
String responseXml = restClient.post()
|
||||
.uri(mciUrl)
|
||||
@@ -58,14 +58,14 @@ public class MciEimsSender implements EimsSender {
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
|
||||
log.info("🌐 [ESB 어댑터] 응답 수신 완료: {}", responseXml);
|
||||
log.info(" [ESB 어댑터] 응답 수신 완료: {}", responseXml);
|
||||
|
||||
JsonNode responseNode = xmlMapper.readTree(responseXml);
|
||||
return jsonMapper.writeValueAsString(responseNode);
|
||||
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
log.info("📊 [SLA 모니터링 - MCI] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
log.info(" [SLA 모니터링 - MCI] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class MciStringEimsSender implements EimsSender {
|
||||
StopWatch stopWatch = new StopWatch(); stopWatch.start();
|
||||
|
||||
try {
|
||||
log.info("🌐 [ESB 어댑터(String)] 전송 준비 완료 - RestClient 호출 시작 (Interface: {})", interfaceId);
|
||||
log.info(" [ESB 어댑터(String)] 전송 준비 완료 - RestClient 호출 시작 (Interface: {})", interfaceId);
|
||||
|
||||
String response = restClient.post()
|
||||
.uri(mciUrl)
|
||||
@@ -47,12 +47,12 @@ public class MciStringEimsSender implements EimsSender {
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
|
||||
log.info("🌐 [ESB 어댑터(String)] 응답 수신 완료: {}", response);
|
||||
log.info(" [ESB 어댑터(String)] 응답 수신 완료: {}", response);
|
||||
return response != null ? response : "";
|
||||
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
log.info("📊 [SLA 모니터링 - MCI(String)] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
log.info(" [SLA 모니터링 - MCI(String)] 소요시간: {} ms", stopWatch.getTotalTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class TcpEimsSender implements EimsSender {
|
||||
|
||||
@Override
|
||||
public String send(String interfaceId, String payload) throws Exception {
|
||||
log.info("🔌 [TCP 소켓 모드] EIMS 접속 중... {}:{}", host, port);
|
||||
log.info(" [TCP 소켓 모드] EIMS 접속 중... {}:{}", host, port);
|
||||
|
||||
// TCP 소켓 자원을 사용 후 안전하게 닫아주는 try-with-resources 구문
|
||||
try (Socket socket = new Socket()) {
|
||||
|
||||
@@ -42,25 +42,25 @@ public class MciTemplate {
|
||||
* @throws MciCommunicationException 통신 또는 파싱 실패 시 예외 발생
|
||||
*/
|
||||
public <T, R> R call(String interfaceId, T requestDto, Class<R> responseType) {
|
||||
log.info("📞 [MciTemplate] 시작 - Interface ID: {}", interfaceId);
|
||||
log.info(" [MciTemplate] 시작 - Interface ID: {}", interfaceId);
|
||||
|
||||
try {
|
||||
String payload = objectMapper.writeValueAsString(requestDto);
|
||||
log.debug("📤 [MciTemplate] 전송 페이로드: {}", payload);
|
||||
log.debug(" [MciTemplate] 전송 페이로드: {}", payload);
|
||||
|
||||
String responseJson = httpEimsSender.send(interfaceId, payload);
|
||||
log.debug("📥 [MciTemplate] 수신 응답 JSON: {}", responseJson);
|
||||
log.debug(" [MciTemplate] 수신 응답 JSON: {}", responseJson);
|
||||
|
||||
R response = objectMapper.readValue(responseJson, responseType);
|
||||
log.info("✅ [MciTemplate] 완료 - Interface ID: {}", interfaceId);
|
||||
log.info(" [MciTemplate] 완료 - Interface ID: {}", interfaceId);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("❌ [MciTemplate] JSON 변환 중 오류 발생", e);
|
||||
log.error(" [MciTemplate] JSON 변환 중 오류 발생", e);
|
||||
throw new MciCommunicationException("MCI 통신 중 JSON 파싱 오류", e);
|
||||
} catch (Exception e) {
|
||||
log.error("❌ [MciTemplate] 통신 중 오류 발생", e);
|
||||
log.error(" [MciTemplate] 통신 중 오류 발생", e);
|
||||
throw new MciCommunicationException("MCI 통신 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class TicketManager {
|
||||
|
||||
ticketStore.put(ticketId, ticket);
|
||||
|
||||
log.info("🎫 [TicketManager] 비동기 작업 티켓 발급 완료: {}", ticketId);
|
||||
log.info(" [TicketManager] 비동기 작업 티켓 발급 완료: {}", ticketId);
|
||||
|
||||
// 10초 뒤에 자동으로 작업을 완료 상태로 변경하는 백그라운드 시뮬레이터 실행
|
||||
simulateEaiProcessing(ticketId, interfaceId);
|
||||
@@ -74,7 +74,7 @@ public class TicketManager {
|
||||
// 가짜 최종 결과 데이터 주입
|
||||
ticket.setResultData("{\"resultCode\":\"0000\", \"interfaceId\":\"" + interfaceId + "\", \"processedRecords\":50000}");
|
||||
ticketStore.put(ticketId, ticket);
|
||||
log.info("🏁 [TicketManager] EAI 비동기 작업 시뮬레이션 완료! (Ticket: {})", ticketId);
|
||||
log.info(" [TicketManager] EAI 비동기 작업 시뮬레이션 완료! (Ticket: {})", ticketId);
|
||||
}
|
||||
}, 10, TimeUnit.SECONDS); // 10초 지연
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ public class ToolSlaMonitoringAspect {
|
||||
|
||||
// SLA 기준을 초과하면 (예: 2초 이상) 경고 로깅 처리 가능
|
||||
if (timeMillis > 2000) {
|
||||
log.warn("🚨 [SLA 경고] Tool: {} | 소요시간: {}ms | 상태: SLOW_RESPONSE", finalName, timeMillis);
|
||||
log.warn(" [SLA 경고] Tool: {} | 소요시간: {}ms | 상태: SLOW_RESPONSE", finalName, timeMillis);
|
||||
} else {
|
||||
log.info("📊 [SLA 추적] Tool: {} | 소요시간: {}ms | 상태: SUCCESS", finalName, timeMillis);
|
||||
log.info(" [SLA 추적] Tool: {} | 소요시간: {}ms | 상태: SUCCESS", finalName, timeMillis);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -74,7 +74,7 @@ public class ToolSlaMonitoringAspect {
|
||||
long timeMillis = stopWatch.getTotalTimeMillis();
|
||||
|
||||
// 에러 발생 시 명확하게 실패 로그 기록
|
||||
log.error("💥 [SLA 장애] Tool: {} | 소요시간: {}ms | 상태: FAILED | 사유: {}", finalName, timeMillis, e.getMessage());
|
||||
log.error(" [SLA 장애] Tool: {} | 소요시간: {}ms | 상태: FAILED | 사유: {}", finalName, timeMillis, e.getMessage());
|
||||
|
||||
// 원래 흐름대로 예외를 던져서 게이트웨이나 상위 로직이 에러를 처리하게 함
|
||||
throw e;
|
||||
|
||||
@@ -27,9 +27,9 @@ public class SampleMciMapperTest {
|
||||
SampleMciReqDto mciDto = sampleMciMapper.toMciReq(aiDto);
|
||||
|
||||
// then: 콘솔에 결과 출력 및 값 검증
|
||||
System.out.println("====== 🚀 MapStruct 변환 테스트 결과 🚀 ======");
|
||||
System.out.println("✅ [변환 전] AI DTO : " + aiDto);
|
||||
System.out.println("✅ [변환 후] MCI DTO: " + mciDto);
|
||||
System.out.println("====== MapStruct 변환 테스트 결과 ======");
|
||||
System.out.println(" [변환 전] AI DTO : " + aiDto);
|
||||
System.out.println(" [변환 후] MCI DTO: " + mciDto);
|
||||
System.out.println("===============================================");
|
||||
|
||||
assertNotNull(mciDto, "변환된 객체는 null이 아니어야 합니다.");
|
||||
|
||||
@@ -41,7 +41,7 @@ public class BillingProcessService extends AbstractMcpToolService {
|
||||
}
|
||||
|
||||
private Object executeBillingLogic(String mappingId, Object data) {
|
||||
log.info("💰 [Billing] 청구 처리 전용 커스텀 전/후처리 로직 수행 시작");
|
||||
log.info(" [Billing] 청구 처리 전용 커스텀 전/후처리 로직 수행 시작");
|
||||
|
||||
Map<String, Object> payload;
|
||||
if (data == null) {
|
||||
@@ -53,7 +53,7 @@ public class BillingProcessService extends AbstractMcpToolService {
|
||||
|
||||
// 커스텀 전처리
|
||||
payload.put("custom_injected_data", "Billing System Check OK");
|
||||
log.info("💰 [Billing] 커스텀 파라미터 주입 완료");
|
||||
log.info(" [Billing] 커스텀 파라미터 주입 완료");
|
||||
|
||||
// 부모 클래스의 레거시 공통 연동 메서드 호출 (PII 마스킹 포함)
|
||||
Map<String, Object> result = executeLegacy("MCI", mappingId, payload);
|
||||
|
||||
34
remove_emojis.py
Normal file
34
remove_emojis.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
pattern = re.compile(r'[\u2600-\u27BF\U0001F000-\U0001F9FF]+')
|
||||
|
||||
root_dir = "."
|
||||
target_exts = {'.java', '.html', '.md', '.yml', '.properties', '.xml', '.json', '.js', '.css'}
|
||||
|
||||
count = 0
|
||||
for subdir, dirs, files in os.walk(root_dir):
|
||||
if '.git' in dirs: dirs.remove('.git')
|
||||
if 'build' in dirs: dirs.remove('build')
|
||||
if '.gradle' in dirs: dirs.remove('.gradle')
|
||||
|
||||
for file in files:
|
||||
ext = os.path.splitext(file)[1].lower()
|
||||
if ext in target_exts:
|
||||
filepath = os.path.join(subdir, file)
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
new_content = pattern.sub('', content)
|
||||
new_content = new_content.replace('\uFE0F', '').replace('\u200D', '')
|
||||
|
||||
if new_content != content:
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
print(f"Cleaned: {filepath}")
|
||||
count += 1
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
print(f"\nTotal files cleaned: {count}")
|
||||
Reference in New Issue
Block a user