From f0cffdf9da6bbf24065569bdf55098287db7663a Mon Sep 17 00:00:00 2001 From: Rinjae Date: Wed, 26 Nov 2025 12:02:08 +0900 Subject: [PATCH 1/6] =?UTF-8?q?KjbProperty=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eactive/ext/kjb/common/KjbProperty.java | 2 +- .../ext/kjb/ums/test/KjbPropertyTest.java | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/eactive/ext/kjb/ums/test/KjbPropertyTest.java diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java index 7b3459b..4348662 100644 --- a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java +++ b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java @@ -73,7 +73,7 @@ public class KjbProperty { @KjbPropertyValue(key = "kjb.obp.url") @Pattern(regexp = "^(https?://[^\\s/$.?#][^\\s]*)|\\s*$", message = "kjb.obp.url 는 유효한 URL 형식이거나 공백이어야 합니다. 예: http://example.com/api") - private int obpUrl; + private String obpUrl; @KjbPropertyValue(key = "kjb.obp.trust_system.key", defaultValue = "x-obp-trust-system") @NotEmpty diff --git a/src/test/java/com/eactive/ext/kjb/ums/test/KjbPropertyTest.java b/src/test/java/com/eactive/ext/kjb/ums/test/KjbPropertyTest.java new file mode 100644 index 0000000..9bf6d78 --- /dev/null +++ b/src/test/java/com/eactive/ext/kjb/ums/test/KjbPropertyTest.java @@ -0,0 +1,28 @@ +package com.eactive.ext.kjb.ums.test; + +import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.utils.ValidationUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; + +@Slf4j +public class KjbPropertyTest { + + @Test + void testKjbProperty() { + Assumptions.assumeTrue( + "true".equalsIgnoreCase(System.getenv("KJB_EAPIM_TEST_ENABLED")), + "OS 환경변수 내 KJB_EAPIM_TEST_ENABLED=TRUE 설정되어 있어야 동작함"); + + + log.info("KjbProperty 테스트 시작"); + + + log.info("기본값 Validation 테스트"); + KjbProperty prop = new KjbProperty(); + ValidationUtil.validateOrThrow(prop); + + log.info("KjbProperty 테스트 완료"); + } +} From 818155242f4d62bdcd6a0e4045e546ae4b244704 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Wed, 26 Nov 2025 14:28:12 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=ED=8C=8C=ED=8A=B8=EB=84=88=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 112 +++++++++++ .../ext/kjb/common/KjbObpException.java | 22 +++ .../eactive/ext/kjb/common/KjbProperty.java | 12 ++ .../com/eactive/ext/kjb/obp/KjbObpModule.java | 78 ++++++-- .../com/eactive/ext/kjb/obp/ObpClient.java | 183 ++++++++++++++++++ .../eactive/ext/kjb/obp/ObpOrganization.java | 121 ++++++++++++ .../com/eactive/ext/kjb/obp/ObpRequest.java | 4 + .../ext/kjb/obp/{common => }/ObpResponse.java | 2 +- .../eactive/ext/kjb/obp/common/ObpClient.java | 22 --- 9 files changed, 518 insertions(+), 38 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/main/java/com/eactive/ext/kjb/common/KjbObpException.java create mode 100644 src/main/java/com/eactive/ext/kjb/obp/ObpClient.java create mode 100644 src/main/java/com/eactive/ext/kjb/obp/ObpOrganization.java create mode 100644 src/main/java/com/eactive/ext/kjb/obp/ObpRequest.java rename src/main/java/com/eactive/ext/kjb/obp/{common => }/ObpResponse.java (84%) delete mode 100644 src/main/java/com/eactive/ext/kjb/obp/common/ObpClient.java diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6b191af --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 프로젝트 개요 + +`eapim-admin-kjb`는 광주은행(KJB) 전용 커스터마이징을 위한 eapim-admin 확장 모듈입니다. 빠른 부팅 속도를 위해 메인 eapim-admin 프로젝트에서 분리되어 독립적으로 테스트할 수 있습니다. + +## 빌드 및 테스트 명령어 + +```bash +# 빌드 +gradle build +gradle clean build + +# 테스트 실행 (환경변수 필요) +KJB_EAPIM_TEST_ENABLED=TRUE gradle :eapim-admin-kjb:test + +# 특정 테스트 클래스 실행 +KJB_EAPIM_TEST_ENABLED=TRUE gradle :eapim-admin-kjb:test --tests "com.eactive.ext.kjb.ums.test.UmsTest.testSms" +KJB_EAPIM_TEST_ENABLED=TRUE gradle :eapim-admin-kjb:test --tests "com.eactive.ext.kjb.ums.test.UmsTest.testEmail" + +# SafeDB 실제 모드로 테스트 (Linux 서버) +KJB_EAPIM_TEST_ENABLED=TRUE gradle :eapim-admin-kjb:test -Psafedb +``` + +**테스트 실행 조건**: `KJB_EAPIM_TEST_ENABLED=TRUE` 환경변수가 설정되어야 UMS/이메일 테스트가 실행됩니다. + +## 모듈 의존성 + +``` +eapim-admin-kjb +├── elink-portal-common # JPA 엔티티 (MessageRequest, MessageCode 등) +└── kjb-safedb # 암호화 라이브러리 +``` + +의존 모듈들은 `../elink-portal-common/`, `../kjb-safedb/` 경로에 위치합니다. + +## 아키텍처 + +### 주요 컴포넌트 + +| 컴포넌트 | 위치 | 설명 | +|---------|------|------| +| `KjbProperty` | `common/` | DB 테이블 `TSEAIRM24`에서 로드되는 설정 관리. `@KjbPropertyValue` 애노테이션으로 키 매핑 | +| `KjbEaiBatchModule` | `eai/` | EAI 배치 시스템 HTTP 연동. 파일 기반 배치 전송 및 보관 기간 관리 | +| `KjbUmsService` | `ums/` | UMS API를 통한 카카오 알림톡 발송 | +| `KjbEmailSendModule` | `ums/` | EAI 배치를 통한 이메일 발송. 파일 생성 후 배치 호출 | +| `KjbObpModule` | `obp/` | OBP(Open Banking Platform) API 연동 | + +### 프로퍼티 키 목록 (`KjbProperty.java`) + +**EAI 배치 연동** +- `kjb.eai.batch.url` - EAI 배치 서버 URL +- `kjb.eai.batch.connection_timeout` - 연결 타임아웃 (초, 기본값: 5) +- `kjb.eai.batch.response_timeout` - 응답 타임아웃 (초, 기본값: 5) +- `kjb.eai.batch.charset` - 문자셋 (기본값: euc-kr) + +**이메일 발송 (EAI 배치 경유)** +- `kjb.ums.eai_batch.send_dir` - 발송 파일 디렉토리 +- `kjb.ums.eai_batch.backup_dir` - 백업 디렉토리 +- `kjb.ums.eai_batch.error_dir` - 에러 디렉토리 +- `kjb.ums.eai_batch.interface_id` - 인터페이스 ID (13자리) +- `kjb.ums.eai_batch.retention_date` - 파일 보관 기간 (일, 기본값: 60) + +**UMS (카카오 알림톡)** +- `kjb.ums.host` - UMS API 엔드포인트 +- `kjb.ums.api_key` - API 인증 키 (36자리 UUID 형식) +- `kjb.ums.connection_timeout` - 연결 타임아웃 (초, 기본값: 5) +- `kjb.ums.response_timeout` - 응답 타임아웃 (초, 기본값: 5) + +**OBP 연동** +- `kjb.obp.url` - OBP API URL +- `kjb.obp.trust_system.key/value` - 신뢰 시스템 헤더 +- `kjb.obp.partner_code.key/value` - 파트너 코드 헤더 + +### 메시지 채널 + +- `KjbUmsService`: 카카오 알림톡 (`MessageCode.Channels.KAKAO_ALIMTALK`) +- `KjbEmailSendModule`: 이메일 (`MessageCode.Channels.EMAIL`) + +## 로컬 개발 환경 (Windows) + +테스트 시 `OsUtil.isWindows()` 조건으로 로컬 경로가 자동 설정됩니다: +- 발송 디렉토리: `c:\kjb-logs\sendmail\snd` +- 백업 디렉토리: `c:\kjb-logs\sendmail\bak` +- 에러 디렉토리: `c:\kjb-logs\sendmail\error` + +URL이 빈 문자열로 설정되면 실제 API 호출 없이 모의 응답을 반환합니다. + +## 기술 스택 + +- **Java 8**: Gradle toolchain 강제 +- **Spring Framework 5.3**: 의존성 주입 +- **Apache HttpClient 5.1**: HTTP 통신 +- **Gson 2.3.1**: JSON 처리 +- **Lombok 1.18.28**: 보일러플레이트 코드 생성 +- **JUnit 5**: 테스트 프레임워크 +- **Logback 1.2.10**: 로깅 + +## 패키지 구조 + +``` +com.eactive.ext.kjb +├── common/ # 공통 (KjbProperty, 예외 클래스) +├── eai/ # EAI 배치 연동 +├── obp/ # OBP API 연동 +│ └── common/ # OBP HTTP 클라이언트 +├── ums/ # UMS/이메일 발송 +│ └── gson/ # UMS 요청/응답 JSON 모델 +└── utils/ # 유틸리티 (JSON, 검증, OS 판별) +``` diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbObpException.java b/src/main/java/com/eactive/ext/kjb/common/KjbObpException.java new file mode 100644 index 0000000..93d9e8b --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/common/KjbObpException.java @@ -0,0 +1,22 @@ +package com.eactive.ext.kjb.common; + +public class KjbObpException extends Exception { + private int statusCode; + + public KjbObpException(String message) { + super(message); + } + + public KjbObpException(String message, Throwable cause) { + super(message, cause); + } + + public KjbObpException(int statusCode, String message) { + super(message); + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } +} diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java index 4348662..a560060 100644 --- a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java +++ b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java @@ -91,6 +91,18 @@ public class KjbProperty { @NotEmpty private String obpPartnerCodeValue = "100001-01"; + @KjbPropertyValue(key = "kjb.obp.connection_timeout", defaultValue = "5") + @Min(1) @Max(10) + private int obpConnectionTimeout = 5; + + @KjbPropertyValue(key = "kjb.obp.response_timeout", defaultValue = "5") + @Min(1) @Max(30) + private int obpResponseTimeout = 5; + + @KjbPropertyValue(key = "kjb.obp.timeout", defaultValue = "10") + @Min(1) @Max(30) + private int obpTimeout = 10; + @KjbPropertyValue(key = "kjb.obp.url.commission", defaultValue = "/api/billing/billing/findBillingForCondition") @NotEmpty diff --git a/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java b/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java index 2cb1340..9e4dcab 100644 --- a/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java +++ b/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java @@ -1,36 +1,84 @@ package com.eactive.ext.kjb.obp; -import com.google.gson.JsonObject; +import com.eactive.ext.kjb.common.KjbObpException; +import com.eactive.ext.kjb.common.KjbProperty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import java.util.Arrays; -import java.util.stream.Collectors; - @Slf4j public class KjbObpModule { + private static final String FAKE_PARTNER_CODE = "999999-99"; + + private static KjbObpModule _instance; + + private final KjbProperty property; + private final ObpClient client; + + public static synchronized KjbObpModule getInstance(KjbProperty kjbProperty) { + if (_instance == null) { + _instance = new KjbObpModule(kjbProperty); + } + return _instance; + } + + private KjbObpModule(KjbProperty kjbProperty) { + if (kjbProperty == null) { + throw new IllegalArgumentException("KjbProperty cannot be null"); + } + this.property = kjbProperty; + this.client = ObpClient.getInstance(kjbProperty); + } + /** - * Trim, 숫자이외 자동 치환 - * @param bizNo - * @return + * 사업자등록번호로 partnerCode 조회 + * @param bizNo 사업자등록번호 (숫자 외 문자 자동 제거) + * @return partnerCode + * @throws KjbObpException 조회 실패 시 */ - public static JsonObject queryOrganization(String bizNo) { + public String queryPartnerCode(String bizNo) throws KjbObpException { bizNo = ObpUtil.onlyDigit(bizNo); if (StringUtils.isEmpty(bizNo)) { - throw new IllegalArgumentException("BizNo is not empty."); + throw new IllegalArgumentException("BizNo is empty."); } if (bizNo.length() != 10) { - throw new IllegalArgumentException("BizNo is not valid."); + throw new IllegalArgumentException("BizNo must be 10 digits. (input: " + bizNo + ")"); } - return null; + // 가상모드: URL 미설정 시 가상 파트너코드 반환 + if (!client.isRealMode()) { + log.info("[OBP] 가상모드 - 가상 partnerCode 반환: {}", FAKE_PARTNER_CODE); + return FAKE_PARTNER_CODE; + } + + String path = String.format(property.getObpUrlOrganizationFmt(), bizNo); + log.debug("[OBP] queryPartnerCode - bizNo: {}, path: {}", bizNo, path); + + ObpResponse response = client.get(path); + + if (response.getStatus() != 200) { + log.warn("[OBP] queryPartnerCode failed - status: {}, body: {}", response.getStatus(), response.getBody()); + throw new KjbObpException(response.getStatus(), "OBP 조회 실패: " + response.getBody()); + } + + ObpOrganization org = ObpOrganization.fromResponse(response); + + if (org == null) { + throw new KjbObpException("OBP 응답 파싱 실패"); + } + + if (!org.hasPartnerCode()) { + throw new KjbObpException("partnerCode가 없습니다. bizNo: " + bizNo); + } + + return org.getPartnerCode(); } - - public static void main(String[] args) { - log.info("queryOrganization Test"); - queryOrganization("123asd123asd123"); + /** + * ObpClient 반환 (직접 API 호출용) + */ + public ObpClient getClient() { + return client; } } diff --git a/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java b/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java new file mode 100644 index 0000000..8618f33 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java @@ -0,0 +1,183 @@ +package com.eactive.ext.kjb.obp; + +import com.eactive.ext.kjb.common.KjbProperty; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.EntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.Timeout; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.*; + + +@Slf4j +public class ObpClient { + private static ObpClient _instance; + + private final KjbProperty property; + private final CloseableHttpClient httpClient; + private final RequestConfig requestConfig; + private final boolean isRealMode; + + public synchronized static ObpClient getInstance(KjbProperty kjbProperty) { + if (_instance == null) { + _instance = new ObpClient(kjbProperty); + } + + return _instance; + } + + private ObpClient(KjbProperty kjbProperty) { + if (kjbProperty == null) { + throw new IllegalArgumentException("KjbProperty cannot be null"); + } + + this.property = kjbProperty; + this.isRealMode = StringUtils.isNotEmpty(kjbProperty.getObpUrl()); + + this.requestConfig = RequestConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(kjbProperty.getObpConnectionTimeout())) + .setResponseTimeout(Timeout.ofSeconds(kjbProperty.getObpResponseTimeout())) + .build(); + + if (isRealMode) { + this.httpClient = HttpClients.createDefault(); + } else { + this.httpClient = null; + log.warn("[OBP] URL이 설정되지 않음. 실제 OBP 연동 없이 동작함"); + } + } + + /** + * GET 요청 + * @param path API 경로 (예: /api/customer/...) + * @return ObpResponse + */ + public ObpResponse get(String path) { + return get(path, null); + } + + /** + * GET 요청 (추가 헤더 포함) + * @param path API 경로 + * @param additionalHeaders 추가 헤더 + * @return ObpResponse + */ + public ObpResponse get(String path, Map additionalHeaders) { + String url = property.getObpUrl() + path; + HttpGet httpGet = new HttpGet(url); + applyDefaultHeaders(httpGet); + applyAdditionalHeaders(httpGet, additionalHeaders); + httpGet.setConfig(requestConfig); + + return execute(httpGet); + } + + /** + * POST 요청 (JSON body) + * @param path API 경로 + * @param jsonBody JSON 문자열 + * @return ObpResponse + */ + public ObpResponse post(String path, String jsonBody) { + return post(path, jsonBody, null); + } + + /** + * POST 요청 (JSON body, 추가 헤더 포함) + * @param path API 경로 + * @param jsonBody JSON 문자열 + * @param additionalHeaders 추가 헤더 + * @return ObpResponse + */ + public ObpResponse post(String path, String jsonBody, Map additionalHeaders) { + String url = property.getObpUrl() + path; + HttpPost httpPost = new HttpPost(url); + applyDefaultHeaders(httpPost); + applyAdditionalHeaders(httpPost, additionalHeaders); + httpPost.setConfig(requestConfig); + + if (StringUtils.isNotEmpty(jsonBody)) { + httpPost.setEntity(EntityBuilder.create() + .setText(jsonBody) + .setContentType(ContentType.APPLICATION_JSON) + .build()); + } + + return execute(httpPost); + } + + private void applyDefaultHeaders(HttpUriRequestBase request) { + request.setHeader("Content-Type", "application/json; charset=utf-8"); + request.setHeader(property.getObpTrustSystemKey(), property.getObpTrustSystemValue()); + request.setHeader(property.getObpPartnerCodeKey(), property.getObpPartnerCodeValue()); + } + + private void applyAdditionalHeaders(HttpUriRequestBase request, Map additionalHeaders) { + if (additionalHeaders != null) { + additionalHeaders.forEach(request::setHeader); + } + } + + private ObpResponse execute(HttpUriRequestBase request) { + if (!isRealMode) { + log.warn("[OBP] URL 미설정으로 실제 호출하지 않음. 요청: {} {}", request.getMethod(), request.getRequestUri()); + ObpResponse mockResponse = new ObpResponse(); + mockResponse.setStatus(200); + mockResponse.setBody("{}"); + return mockResponse; + } + + ExecutorService executor = Executors.newSingleThreadExecutor(); + + try { + log.debug("[OBP] 요청: {} {}", request.getMethod(), request.getRequestUri()); + + Future future = executor.submit(() -> + httpClient.execute(request, response -> { + ObpResponse obpResponse = new ObpResponse(); + obpResponse.setStatus(response.getCode()); + obpResponse.setBody(EntityUtils.toString(response.getEntity(), "UTF-8")); + + Arrays.asList(response.getHeaders()).forEach(header -> + obpResponse.getHeaders().put(header.getName(), header.getValue())); + + return obpResponse; + }) + ); + + ObpResponse response = future.get(property.getObpTimeout(), TimeUnit.SECONDS); + + if (log.isDebugEnabled()) { + log.debug("[OBP] 응답: status={}, body={}", response.getStatus(), response.getBody()); + } + + return response; + + } catch (InterruptedException | ExecutionException | TimeoutException e) { + log.error("[OBP] 호출 실패: {} {}", request.getMethod(), request.getRequestUri(), e); + ObpResponse errorResponse = new ObpResponse(); + errorResponse.setStatus(-1); + errorResponse.setBody(e.getMessage()); + return errorResponse; + } finally { + executor.shutdown(); + } + } + + /** + * 실제 모드 여부 + */ + public boolean isRealMode() { + return isRealMode; + } +} diff --git a/src/main/java/com/eactive/ext/kjb/obp/ObpOrganization.java b/src/main/java/com/eactive/ext/kjb/obp/ObpOrganization.java new file mode 100644 index 0000000..13625e4 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpOrganization.java @@ -0,0 +1,121 @@ +package com.eactive.ext.kjb.obp; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +/** + * OBP Organization 조회 응답 + * - 필수 필드만 명시적으로 선언하고, 나머지는 rawJson으로 보관하여 유연성 확보 + */ +@Data +public class ObpOrganization { + private static final Gson gson = new Gson(); + + // 핵심 필드 + @SerializedName("partnerCode") + private String partnerCode; + + @SerializedName("id") + private String id; + + @SerializedName("name") + private String name; + + @SerializedName("state") + private String state; + + @SerializedName("status") + private String status; + + @SerializedName("businessManRegistrationNo") + private String businessManRegistrationNo; + + @SerializedName("corporateRegistrationNo") + private String corporateRegistrationNo; + + @SerializedName("representativeName") + private String representativeName; + + // 원본 JSON (추가 필드 접근용 + private transient JsonObject rawJson; + + /** + * JSON 문자열에서 파싱 + */ + public static ObpOrganization fromJson(String json) { + if (json == null || json.trim().isEmpty()) { + return null; + } + + ObpOrganization org = gson.fromJson(json, ObpOrganization.class); + org.rawJson = new JsonParser().parse(json).getAsJsonObject(); + return org; + } + + /** + * ObpResponse에서 파싱 + */ + public static ObpOrganization fromResponse(ObpResponse response) { + if (response == null || response.getStatus() != 200) { + return null; + } + return fromJson(response.getBody()); + } + + /** + * 원본 JSON에서 추가 필드 조회 + * @param fieldName 필드명 (예: "englishName", "description") + * @return 필드 값 (없으면 null) + */ + public String getField(String fieldName) { + if (rawJson == null || !rawJson.has(fieldName) || rawJson.get(fieldName).isJsonNull()) { + return null; + } + return rawJson.get(fieldName).getAsString(); + } + + /** + * 원본 JSON에서 중첩 필드 조회 + * @param path 경로 (예: "type.name", "type.parent.name") + * @return 필드 값 (없으면 null) + */ + public String getNestedField(String path) { + if (rawJson == null || path == null) { + return null; + } + + String[] parts = path.split("\\."); + JsonObject current = rawJson; + + for (int i = 0; i < parts.length - 1; i++) { + if (!current.has(parts[i]) || current.get(parts[i]).isJsonNull()) { + return null; + } + current = current.getAsJsonObject(parts[i]); + } + + String lastField = parts[parts.length - 1]; + if (!current.has(lastField) || current.get(lastField).isJsonNull()) { + return null; + } + + return current.get(lastField).getAsString(); + } + + /** + * 원본 JSON 객체 반환 + */ + public JsonObject getRawJson() { + return rawJson; + } + + /** + * partnerCode 유효성 확인 + */ + public boolean hasPartnerCode() { + return partnerCode != null && !partnerCode.trim().isEmpty(); + } +} diff --git a/src/main/java/com/eactive/ext/kjb/obp/ObpRequest.java b/src/main/java/com/eactive/ext/kjb/obp/ObpRequest.java new file mode 100644 index 0000000..287e4f3 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpRequest.java @@ -0,0 +1,4 @@ +package com.eactive.ext.kjb.obp; + +public class ObpRequest { +} diff --git a/src/main/java/com/eactive/ext/kjb/obp/common/ObpResponse.java b/src/main/java/com/eactive/ext/kjb/obp/ObpResponse.java similarity index 84% rename from src/main/java/com/eactive/ext/kjb/obp/common/ObpResponse.java rename to src/main/java/com/eactive/ext/kjb/obp/ObpResponse.java index ab52836..ce32e47 100644 --- a/src/main/java/com/eactive/ext/kjb/obp/common/ObpResponse.java +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpResponse.java @@ -1,4 +1,4 @@ -package com.eactive.ext.kjb.obp.common; +package com.eactive.ext.kjb.obp; import lombok.Data; diff --git a/src/main/java/com/eactive/ext/kjb/obp/common/ObpClient.java b/src/main/java/com/eactive/ext/kjb/obp/common/ObpClient.java deleted file mode 100644 index f0b04c3..0000000 --- a/src/main/java/com/eactive/ext/kjb/obp/common/ObpClient.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.eactive.ext.kjb.obp.common; - -import com.eactive.ext.kjb.common.KjbProperty; -import lombok.extern.slf4j.Slf4j; - - -@Slf4j -public class ObpClient { - private static ObpClient _instance; - - public synchronized static ObpClient getInstance(KjbProperty kjbProperty) { - if (_instance == null) { - _instance = new ObpClient(kjbProperty); - } - - return _instance; - } - - private ObpClient(KjbProperty kjbProperty) { - - } -} From 4f811fc48b69c298528cead18d31a16f4f1ef315 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Wed, 26 Nov 2025 21:49:09 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Revert=20"=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e894d59154ef9f7e257345cc0cd2fbb498dbbe41. --- src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java b/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java index e1ec735..64f1255 100644 --- a/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java +++ b/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java @@ -145,9 +145,9 @@ public class UmsTest { MessageRequest messageRequest = new MessageRequest(); messageRequest.setId(UUID.randomUUID().toString()); messageRequest.setMessageCode(messageCode); - messageRequest.setEmail("1920201@kjbank.com"); + messageRequest.setEmail("dearmai@dearmai.net"); messageRequest.setUmsUid(UUID.randomUUID().toString()); - messageRequest.setUsername("이현지"); + messageRequest.setUsername("김광은"); messageRequest.setSubject("제목 - test"); messageRequest.setMessage(message); // messageRequest.setServiceId(serviceId); From b698147bbad814c21ba4ad95b045b033fa95ba10 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Mon, 8 Dec 2025 10:32:38 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=86=A1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java b/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java index 64f1255..f471f44 100644 --- a/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java +++ b/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java @@ -27,7 +27,7 @@ public class UmsTest { public static void init() { prop = new KjbProperty(); prop.setEaiBatchUrl("http://172.31.32.111:10230/BATAppWeb/EaiBatCall"); - prop.setUmsEaiBatchInterfaceId("UAGFF00001UIE"); + prop.setUmsEaiBatchInterfaceId("UIEFF00001UAG"); prop.setUmsHostUrl("http://172.31.35.144:9021"); prop.setUmsApiKey("7cca6d58-e4f7-474d-9edd-525806bc99ff"); @@ -145,7 +145,7 @@ public class UmsTest { MessageRequest messageRequest = new MessageRequest(); messageRequest.setId(UUID.randomUUID().toString()); messageRequest.setMessageCode(messageCode); - messageRequest.setEmail("dearmai@dearmai.net"); + messageRequest.setEmail("25W0064@kjbank.com"); messageRequest.setUmsUid(UUID.randomUUID().toString()); messageRequest.setUsername("김광은"); messageRequest.setSubject("제목 - test"); From 11c124402255ccea00fbad6a20ab4471a32c7352 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 9 Dec 2025 15:25:05 +0900 Subject: [PATCH 5/6] =?UTF-8?q?OBP=20=EC=97=B0=EA=B3=84=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java | 7 +++++++ src/main/java/com/eactive/ext/kjb/obp/ObpClient.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java b/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java index 9e4dcab..3dd8789 100644 --- a/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java +++ b/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java @@ -2,6 +2,7 @@ package com.eactive.ext.kjb.obp; import com.eactive.ext.kjb.common.KjbObpException; import com.eactive.ext.kjb.common.KjbProperty; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -11,12 +12,18 @@ public class KjbObpModule { private static KjbObpModule _instance; + @Getter private final KjbProperty property; private final ObpClient client; public static synchronized KjbObpModule getInstance(KjbProperty kjbProperty) { if (_instance == null) { _instance = new KjbObpModule(kjbProperty); + } else { + if (kjbProperty.equals(_instance.getProperty())) { + log.debug("KjbProperty 파라미터가 변경되어 KjbObpModule 인스턴스 재 생성"); + _instance = new KjbObpModule(kjbProperty); + } } return _instance; } diff --git a/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java b/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java index 8618f33..26684fb 100644 --- a/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java @@ -1,6 +1,7 @@ package com.eactive.ext.kjb.obp; import com.eactive.ext.kjb.common.KjbProperty; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.hc.client5.http.classic.methods.HttpGet; @@ -23,6 +24,7 @@ import java.util.concurrent.*; public class ObpClient { private static ObpClient _instance; + @Getter private final KjbProperty property; private final CloseableHttpClient httpClient; private final RequestConfig requestConfig; @@ -31,6 +33,11 @@ public class ObpClient { public synchronized static ObpClient getInstance(KjbProperty kjbProperty) { if (_instance == null) { _instance = new ObpClient(kjbProperty); + } else { + if (kjbProperty.equals(_instance.getProperty())) { + log.debug("KjbProperty 파라미터가 변경되어 ObpClient 인스턴스 재 생성"); + _instance = new ObpClient(kjbProperty); + } } return _instance; From 147565784765e2247b2cbf02ebdfee510f63628d Mon Sep 17 00:00:00 2001 From: Rinjae Date: Thu, 11 Dec 2025 18:04:47 +0900 Subject: [PATCH 6/6] =?UTF-8?q?property=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/eactive/ext/kjb/common/KjbProperty.java | 12 ++++++------ .../java/com/eactive/ext/kjb/ums/KjbUmsService.java | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java index d0ed3d4..6b96a9d 100644 --- a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java +++ b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java @@ -29,17 +29,17 @@ public class KjbProperty { @NotEmpty private String eaiBatchCharset = "euc-kr"; - @KjbPropertyValue(key = "kjb.ums.eai_batch.send_dir") + @KjbPropertyValue(key = "kjb.ums.eai_batch.send_dir", defaultValue = "/Data/eapim/portal/sendmail/snd") @NotEmpty(message = "kjb.ums.eai_batch.send_dir 는 필수값입니다.") - private String umsEaiBatchSendDir; + private String umsEaiBatchSendDir = "/Data/eapim/portal/sendmail/snd"; - @KjbPropertyValue(key = "kjb.ums.eai_batch.backup_dir") + @KjbPropertyValue(key = "kjb.ums.eai_batch.backup_dir", defaultValue = "/Data/eapim/portal/sendmail/bak") @NotEmpty(message = "kjb.ums.eai_batch.backup_dir 는 필수값입니다.") - private String umsEaiBatchBackupDir; + private String umsEaiBatchBackupDir = "/Data/eapim/portal/sendmail/bak"; - @KjbPropertyValue(key = "kjb.ums.eai_batch.error_dir") + @KjbPropertyValue(key = "kjb.ums.eai_batch.error_dir", defaultValue = "/Data/eapim/portal/sendmail/bak") @NotEmpty(message = "kjb.ums.eai_batch.error_dir 는 필수값입니다.") - private String umsEaiBatchErrorDir; + private String umsEaiBatchErrorDir = "/Data/eapim/portal/sendmail/bak"; @KjbPropertyValue(key = "kjb.ums.eai_batch.interface_id") @Pattern(regexp = "^[a-zA-Z0-9]{13}$", message = "kjb.ums.eai_batch.interface_id 는 12자리 영문대소문자 및 숫자 조합이어야 합니다.") diff --git a/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java b/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java index c849023..7d1a546 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java +++ b/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java @@ -54,7 +54,12 @@ public class KjbUmsService { isRealMode = property.getUmsHostUrl() != null && StringUtils.isNotEmpty(property.getUmsHostUrl().trim()); if (isRealMode) { - ValidationUtil.validateOrThrow(property); + try { + ValidationUtil.validateOrThrow(property); + } catch (Exception e) { + log.error("[KJB-UMS] 파라미터 설정 오류", e); + throw e; + } httpClient = HttpClients.createDefault(); requestConfig = RequestConfig.custom()