diff --git a/.gitignore b/.gitignore index 25c7ff0..8cf334c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ build/ .settings .classpath .project +out +/.apt_generated/ +/.apt_generated_tests/ 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/bin/.gitignore b/bin/.gitignore index 7eed456..7ac70cd 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -1,2 +1,3 @@ /main/ /test/ +/default/ diff --git a/build.gradle b/build.gradle index d9e6691..e5b9cb4 100644 --- a/build.gradle +++ b/build.gradle @@ -9,16 +9,25 @@ group = 'com.eactive.ext.kjb' version = '1.0-SNAPSHOT' def springVersion = "5.3.27" +def nexusUrl = "https://nexus.eactive.synology.me:8090" +repositories { + maven { + url "${nexusUrl}/repository/maven-public/" + allowInsecureProtocol = true + } +} dependencies { annotationProcessor "org.projectlombok:lombok:1.18.28" api project(':elink-portal-common') - api project(":kjb-safedb") + //api project(":kjb-safedb") api 'com.eactive.elink.common:elink-common-data:4.5.5' + implementation fileTree(dir: "ext-libs", include: ["*.jar"]) + // api "org.springframework:spring-context:${springVersion}" // api "org.springframework:spring-webmvc:${springVersion}" // api "org.springframework.security.oauth:spring-security-oauth2:2.3.8.RELEASE" @@ -27,12 +36,16 @@ dependencies { implementation "org.apache.httpcomponents.client5:httpclient5:5.1" + compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1' + compileOnly 'org.slf4j:jul-to-slf4j:1.7.35' compileOnly 'org.slf4j:jcl-over-slf4j:1.7.35' compileOnly 'org.slf4j:log4j-over-slf4j:1.7.35' compileOnly 'org.slf4j:slf4j-api:1.7.35' - compileOnly fileTree(dir: "ext-libs/compileOnly", include: ["*.jar"]) + compileOnly "org.jdom:jdom:1.1" + + compileOnly fileTree(dir: "ext-libs/compileOnly", include: ["*.jar"]) testAnnotationProcessor "org.projectlombok:lombok:1.18.28" diff --git a/ext-libs/INICrypto_v4.2.10.jar b/ext-libs/INICrypto_v4.2.10.jar new file mode 100644 index 0000000..d4b108d Binary files /dev/null and b/ext-libs/INICrypto_v4.2.10.jar differ diff --git a/ext-libs/external_v4.2.1.00_kjbank18.jar b/ext-libs/external_v4.2.1.00_kjbank18.jar new file mode 100644 index 0000000..eaf35b2 Binary files /dev/null and b/ext-libs/external_v4.2.1.00_kjbank18.jar differ diff --git a/ext-libs/jdom2-2.0.6.jar b/ext-libs/jdom2-2.0.6.jar new file mode 100644 index 0000000..2850ca1 Binary files /dev/null and b/ext-libs/jdom2-2.0.6.jar differ diff --git a/ext-libs/log4j-1.2-api-2.17.2.jar b/ext-libs/log4j-1.2-api-2.17.2.jar new file mode 100644 index 0000000..1f73132 Binary files /dev/null and b/ext-libs/log4j-1.2-api-2.17.2.jar differ diff --git a/ext-libs/log4j-api-2.17.2.jar b/ext-libs/log4j-api-2.17.2.jar new file mode 100644 index 0000000..16d9061 Binary files /dev/null and b/ext-libs/log4j-api-2.17.2.jar differ diff --git a/ext-libs/log4j-core-2.17.2.jar b/ext-libs/log4j-core-2.17.2.jar new file mode 100644 index 0000000..0fd0051 Binary files /dev/null and b/ext-libs/log4j-core-2.17.2.jar differ diff --git a/ext-libs/poolit.jar b/ext-libs/poolit.jar new file mode 100644 index 0000000..272db37 Binary files /dev/null and b/ext-libs/poolit.jar differ diff --git a/ext-libs/xercesImpl-2.12.2.jar b/ext-libs/xercesImpl-2.12.2.jar new file mode 100644 index 0000000..ccbae9f Binary files /dev/null and b/ext-libs/xercesImpl-2.12.2.jar differ diff --git a/kjb-docs/obp/obp-query-partner/README.md b/kjb-docs/obp/obp-query-partner/README.md new file mode 100644 index 0000000..8a6bcd6 --- /dev/null +++ b/kjb-docs/obp/obp-query-partner/README.md @@ -0,0 +1 @@ +- API Client 그대로 구현 \ No newline at end of file diff --git a/kjb-docs/obp/obp-query-partner/organizationFromObp.http b/kjb-docs/obp/obp-query-partner/organizationFromObp.http new file mode 100644 index 0000000..56ce58a --- /dev/null +++ b/kjb-docs/obp/obp-query-partner/organizationFromObp.http @@ -0,0 +1,94 @@ +GET /api/organizations/organizationFromObp?cacheBuster=1764073311139&licenseNumber=1838700854&organizationId=15 HTTP/1.1 +Accept: application/json, text/plain, */* +Accept-Encoding: gzip, deflate +Accept-Language: ko,en;q=0.9,en-US;q=0.8 +Connection: keep-alive +Cookie: XSRF-TOKEN=0e8e6398-9231-4b2b-859a-a0ace85c9f12; NG_TRANSLATE_LANG_KEY=%22ko%22; JSESSIONID_APIPORTAL=DE09F0098148634E05D434D3D8D9EFE5; loginStatus=success; showNotice= +Host: 192.168.246.16:8080 +Referer: http://192.168.246.16:8080/ +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62 +X-XSRF-TOKEN: 0e8e6398-9231-4b2b-859a-a0ace85c9f12 + + +HTTP/1.1 200 +X-Application-Context: portalApplication:swagger,dev:8085 +X-Content-Type-Options: nosniff +X-XSS-Protection: 1; mode=block +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +Content-Type: application/json;charset=UTF-8 +Content-Length: 2126 +Date: Tue, 25 Nov 2025 12:21:51 GMT +Keep-Alive: timeout=20 +Connection: keep-alive + + +{ + "id" : "001-90010001-000001-6000009", + "registeredOn" : "2021-07-19T07:25:53.000+0000", + "registeredBy" : "001-90010009-000000-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "재팬리미트파이낸스㈜", + "state" : "01", + "englishName" : null, + "description" : "기업회원-재팬리미트파이낸스㈜", + "type" : { + "id" : "001-90010001-000001-0000001", + "registeredOn" : "2016-09-06T01:51:18.000+0000", + "registeredBy" : "001-20160721-163701-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "제휴 기업", + "alias" : null, + "description" : "제휴 기업", + "parent" : { + "id" : "001-90010001-000000-0000011", + "registeredOn" : "2016-09-06T01:51:18.000+0000", + "registeredBy" : "001-20160721-163701-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "고객 타입", + "alias" : null, + "description" : "고객 타입", + "parent" : { + "id" : "001-90010001-000000-0000010", + "registeredOn" : "2016-09-06T01:51:18.000+0000", + "registeredBy" : "001-20160721-163701-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "파티", + "alias" : null, + "description" : "파티 최상위 타입 자체", + "parent" : null + } + } + }, + "partnerId" : null, + "status" : "01", + "individualCorporateDivision" : "02", + "informationOfferLevel" : "10", + "signedOnDay" : "20170726", + "signedPath" : "31", + "statusReason" : null, + "addresses" : [ ], + "contacts" : [ ], + "communityInfo" : null, + "individualAgreements" : [ ], + "signup" : null, + "accountabilities" : null, + "businessManRegistrationNo" : "1838700854", + "corporateRegistrationNo" : "1101116464905", + "closeDivision" : "09", + "incorporatedOnDay" : "20170726", + "industryClassification" : null, + "representativeName" : "호세인사로와", + "representativeBirthday" : null, + "typeOfBusiness" : null, + "businessCondition" : null, + "partnerCode" : "000014-01", + "corporateEmployee" : [ ], + "defaultAddress" : null, + "defaultContact" : null +} \ No newline at end of file diff --git a/kjb-docs/obp/obp-query-partner/organizationFromObp.md b/kjb-docs/obp/obp-query-partner/organizationFromObp.md new file mode 100644 index 0000000..d0bef76 --- /dev/null +++ b/kjb-docs/obp/obp-query-partner/organizationFromObp.md @@ -0,0 +1,70 @@ +```json +{ + "id" : "001-90010001-000001-6000009", + "registeredOn" : "2021-07-19T07:25:53.000+0000", + "registeredBy" : "001-90010009-000000-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "재팬리미트파이낸스㈜", + "state" : "01", + "englishName" : null, + "description" : "기업회원-재팬리미트파이낸스㈜", + "type" : { + "id" : "001-90010001-000001-0000001", + "registeredOn" : "2016-09-06T01:51:18.000+0000", + "registeredBy" : "001-20160721-163701-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "제휴 기업", + "alias" : null, + "description" : "제휴 기업", + "parent" : { + "id" : "001-90010001-000000-0000011", + "registeredOn" : "2016-09-06T01:51:18.000+0000", + "registeredBy" : "001-20160721-163701-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "고객 타입", + "alias" : null, + "description" : "고객 타입", + "parent" : { + "id" : "001-90010001-000000-0000010", + "registeredOn" : "2016-09-06T01:51:18.000+0000", + "registeredBy" : "001-20160721-163701-0000001", + "modifiedOn" : null, + "modifiedBy" : null, + "name" : "파티", + "alias" : null, + "description" : "파티 최상위 타입 자체", + "parent" : null + } + } + }, + "partnerId" : null, + "status" : "01", + "individualCorporateDivision" : "02", + "informationOfferLevel" : "10", + "signedOnDay" : "20170726", + "signedPath" : "31", + "statusReason" : null, + "addresses" : [ ], + "contacts" : [ ], + "communityInfo" : null, + "individualAgreements" : [ ], + "signup" : null, + "accountabilities" : null, + "businessManRegistrationNo" : "1838700854", + "corporateRegistrationNo" : "1101116464905", + "closeDivision" : "09", + "incorporatedOnDay" : "20170726", + "industryClassification" : null, + "representativeName" : "호세인사로와", + "representativeBirthday" : null, + "typeOfBusiness" : null, + "businessCondition" : null, + "partnerCode" : "000014-01", + "corporateEmployee" : [ ], + "defaultAddress" : null, + "defaultContact" : null +} +``` \ No newline at end of file diff --git a/kjb-docs/obp/organization-commission/commission.http b/kjb-docs/obp/organization-commission/commission.http new file mode 100644 index 0000000..dfb4d85 --- /dev/null +++ b/kjb-docs/obp/organization-commission/commission.http @@ -0,0 +1,151 @@ +POST /api/admin/commission?cacheBuster=1764308323034 HTTP/1.1 +Accept: application/json, text/plain, */* +Accept-Encoding: gzip, deflate +Accept-Language: ko,en;q=0.9,en-US;q=0.8 +Connection: keep-alive +Content-Length: 235 +Content-Type: application/json;charset=UTF-8 +Cookie: XSRF-TOKEN=a8bfa79d-f739-480a-a3d8-ca524efe8928; NG_TRANSLATE_LANG_KEY=%22ko%22; JSESSIONID_APIPORTAL=F53D8E820FAA78EBE1FF7CD197F67650; loginStatus=success; showNotice= +Host: 192.168.246.16:8080 +Origin: http://192.168.246.16:8080 +Referer: http://192.168.246.16:8080/ +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62 +X-XSRF-TOKEN: a8bfa79d-f739-480a-a3d8-ca524efe8928 + +{"provider":{"id":15,"name":"OBP","description":"OBP","type":"PROVIDER","state":"ACTIVE"},"organization":{"id":70,"name":"글로벌머니익스프레스","description":null,"type":"PARTNER","state":"ACTIVE"},"year":"2025","month":"01"} + + +HTTP/1.1 200 +X-Application-Context: portalApplication:swagger,dev:8085 +X-Content-Type-Options: nosniff +X-XSS-Protection: 1; mode=block +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +Content-Type: application/json;charset=UTF-8 +Transfer-Encoding: chunked +Date: Fri, 28 Nov 2025 05:38:43 GMT +Keep-Alive: timeout=20 +Connection: keep-alive + +{ +"status" : "01", +"apiTotalValueSum" : "0.00", +"apiSum" : "0.00", +"apiTotalValueSum2" : "0.00", +"apiSum2" : "0.00", +"productTotalValueCountSum" : "7", +"productTotalValueAmountSum" : "0", +"productSum" : "200000.00", +"productTotalValueCountSum2" : "7", +"productTotalValueAmountSum2" : "0", +"productSum2" : "200000.00", +"totalValueCountSum" : "7.00", +"totalValueCountSum2" : "7.00", +"totalValueAmountSum" : "0", +"totalValueAmountSum2" : "0", +"sum" : "200000.00", +"sum2" : "200000.00", +"list" : [ { +"paymentTargetName" : "예금주성명조회 API", +"paymentTargetType" : "01", +"value" : "100.00", +"minimumAmount" : "0.00", +"maximumAmount" : null, +"totalCount" : "0", +"totalCount2" : "0", +"totalValue" : "0.00", +"totalValue2" : "0.00", +"minimumAmount2" : "0.00", +"maximumAmount2" : null, +"paymentBaseName" : "건", +"paymentTimingName" : "사후부과", +"valueDivisionName" : "금액", +"sum" : "0.00", +"sum2" : "0.00", +"detailFeeTypeName" : null, +"value2" : "100.00", +"changeReason2" : "system billing" +}, { +"paymentTargetName" : "[계좌] 실지명의조회 API", +"paymentTargetType" : "01", +"value" : "100.00", +"minimumAmount" : "0.00", +"maximumAmount" : null, +"totalCount" : "0", +"totalCount2" : "0", +"totalValue" : "0.00", +"totalValue2" : "0.00", +"minimumAmount2" : "0.00", +"maximumAmount2" : null, +"paymentBaseName" : "건", +"paymentTimingName" : "사후부과", +"valueDivisionName" : "금액", +"sum" : "0.00", +"sum2" : "0.00", +"detailFeeTypeName" : null, +"value2" : "100.00", +"changeReason2" : "system billing" +}, { +"paymentTargetName" : "가상계좌 API", +"paymentTargetType" : "02", +"value" : "400.00", +"minimumAmount" : "100000.00", +"maximumAmount" : null, +"totalCount" : "7", +"totalCount2" : "7", +"totalValue" : "7.00", +"totalValue2" : "7.00", +"minimumAmount2" : "100000.00", +"maximumAmount2" : null, +"paymentBaseName" : "건", +"paymentTimingName" : "사후부과", +"valueDivisionName" : "금액", +"sum" : "100000.00", +"sum2" : "100000.00", +"detailFeeTypeName" : "API", +"value2" : "400.00", +"changeReason2" : "system billing" +}, { +"paymentTargetName" : "입금이체(당행) API", +"paymentTargetType" : "02", +"value" : "100.00", +"minimumAmount" : "0.00", +"maximumAmount" : null, +"totalCount" : "0", +"totalCount2" : "0", +"totalValue" : "0.00", +"totalValue2" : "0.00", +"minimumAmount2" : "0.00", +"maximumAmount2" : null, +"paymentBaseName" : "건", +"paymentTimingName" : "사후부과", +"valueDivisionName" : "금액", +"sum" : "0.00", +"sum2" : "0.00", +"detailFeeTypeName" : "API", +"value2" : "100.00", +"changeReason2" : "system billing" +}, { +"paymentTargetName" : "입금이체(타행) API", +"paymentTargetType" : "02", +"value" : "400.00", +"minimumAmount" : "100000.00", +"maximumAmount" : null, +"totalCount" : "0", +"totalCount2" : "0", +"totalValue" : "0.00", +"totalValue2" : "0.00", +"minimumAmount2" : "100000.00", +"maximumAmount2" : null, +"paymentBaseName" : "건", +"paymentTimingName" : "사후부과", +"valueDivisionName" : "금액", +"sum" : "100000.00", +"sum2" : "100000.00", +"detailFeeTypeName" : "API", +"value2" : "400.00", +"changeReason2" : "system billing" +} ], +"documentFormatNo" : null +} \ No newline at end of file diff --git a/kjb-docs/sso/WEB-INF/lib/INICrypto_v4.0.12.jar b/kjb-docs/sso/WEB-INF/lib/INICrypto_v4.0.12.jar new file mode 100644 index 0000000..09636c9 Binary files /dev/null and b/kjb-docs/sso/WEB-INF/lib/INICrypto_v4.0.12.jar differ diff --git a/kjb-docs/sso/WEB-INF/lib/external_v4.2.0.02.jar b/kjb-docs/sso/WEB-INF/lib/external_v4.2.0.02.jar new file mode 100644 index 0000000..99afc6c Binary files /dev/null and b/kjb-docs/sso/WEB-INF/lib/external_v4.2.0.02.jar differ diff --git a/kjb-docs/sso/WEB-INF/lib/jdom.jar b/kjb-docs/sso/WEB-INF/lib/jdom.jar new file mode 100644 index 0000000..4736a4e Binary files /dev/null and b/kjb-docs/sso/WEB-INF/lib/jdom.jar differ diff --git a/kjb-docs/sso/WEB-INF/lib/log4j-1.2.16.jar b/kjb-docs/sso/WEB-INF/lib/log4j-1.2.16.jar new file mode 100644 index 0000000..3f9d847 Binary files /dev/null and b/kjb-docs/sso/WEB-INF/lib/log4j-1.2.16.jar differ diff --git a/kjb-docs/sso/WEB-INF/lib/poolit.jar b/kjb-docs/sso/WEB-INF/lib/poolit.jar new file mode 100644 index 0000000..272db37 Binary files /dev/null and b/kjb-docs/sso/WEB-INF/lib/poolit.jar differ diff --git a/kjb-docs/sso/WEB-INF/lib/xercesImpl.jar b/kjb-docs/sso/WEB-INF/lib/xercesImpl.jar new file mode 100644 index 0000000..33990e8 Binary files /dev/null and b/kjb-docs/sso/WEB-INF/lib/xercesImpl.jar differ diff --git a/kjb-docs/sso/app01.jsp b/kjb-docs/sso/app01.jsp new file mode 100644 index 0000000..0a8e495 --- /dev/null +++ b/kjb-docs/sso/app01.jsp @@ -0,0 +1,48 @@ +<%@ page language="java" contentType="text/html; charset=EUC-KR" + pageEncoding="EUC-KR"%> + + +<% + String user_id = (String)session.getAttribute("SSO_ID"); + String app_id = (String)session.getAttribute("APP_ID"); + String ext_info = (String)session.getAttribute("EXT_INFO"); + String retCode = (String)session.getAttribute("RET_CODE"); + +%> + + + +Insert title here + + +

Single Sign On [<%=user_id %>]

+ + [ 업무시스템 - (SAMPLE_APP)] + + + + + + + + + + + + + + + + + + + + + + + + +
* 사용자ID<%=user_id%>
* SERVICE_NAME<%=app_id%>
* 확장정보<%=ext_info%>
* 인증토큰 상태retCode = [<%=retCode%>] (1000:누락, 1001:시간, 1002:비정상)
+
+ + \ No newline at end of file diff --git a/kjb-docs/sso/config.jsp b/kjb-docs/sso/config.jsp new file mode 100644 index 0000000..653d772 --- /dev/null +++ b/kjb-docs/sso/config.jsp @@ -0,0 +1,295 @@ +<%@ page + import="sun.misc.*, + java.util.*, + java.math.*, + java.net.*, + com.initech.eam.nls.*, + com.initech.eam.api.*, + com.initech.eam.base.*, + com.initech.eam.nls.command.*, + com.initech.eam.smartenforcer.*, + examples.api.*" +%> +<%@page import="com.initech.eam.nls.CookieManager"%> +<%! +/**[INISAFE NEXESS JAVA AGENT]********************************************************************** +* 업무시스템 설정 사항 (업무 환경에 맞게 변경) +***************************************************************************************************/ + + +/***[SERVICE CONFIGURATION]***********************************************************************/ + private String SERVICE_NAME = "Web"; + private String SERVER_URL = "http://test.kjbank.com"; + private String SERVER_PORT = "8080"; + private String ASCP_URL = SERVER_URL + ":" + SERVER_PORT + "/se/login_exec.jsp"; +/*************************************************************************************************/ + + +/***[SSO CONFIGURATION]**]***********************************************************************/ + private String NLS_URL = "http://sso.kjbank.com"; + private String NLS_PORT = "13890"; + private String NLS_LOGIN_URL = NLS_URL + ":" + NLS_PORT + "/nls3/clientLogin.jsp"; + private String NLS_LOGOUT_URL= NLS_URL + ":" + NLS_PORT + "/nls3/NCLogout.jsp"; + private String NLS_ERROR_URL = NLS_URL + ":" + NLS_PORT + "/nls3/error.jsp"; + +/***[SSO ND LIST 운영]**]***********************************************************************/ + private static String ND_URL1 = "http://sso.kjbank.com:5480"; + + + private static Vector PROVIDER_LIST = new Vector(); + + private static final int COOKIE_SESSTION_TIME_OUT = 3000000; + + // 인증 타입 (ID/PW 방식 : 1, 인증서 : 3) + private String TOA = "1"; + private String SSO_DOMAIN = ".kjbank.com"; + + private static final int timeout = 15000; + private static NXContext context = null; + static{ + List serverurlList = new ArrayList(); + serverurlList.add(ND_URL1); + + context = new NXContext(serverurlList,timeout); + CookieManager.setEncStatus(true); + + PROVIDER_LIST.add("sso.kjbank.com"); + SECode.setCookiePadding("_V42"); + } + + // by Kang + public NXContext getContext() + { + NXContext context = null; + try + { + List serverurlList = new ArrayList(); + serverurlList.add(ND_URL1); + context = new NXContext(serverurlList); + CookieManager.setEncStatus(true); + + PROVIDER_LIST.add("sso.kjbank.com"); + //NLS3 web.xml의 CookiePadding 값과 같아야 한다. 안그럼 검증 페일남 + SECode.setCookiePadding("_V42"); + } + catch (Exception e) + { + e.printStackTrace(); + } + return context; + } + + // 통합 SSO ID 조회 + public String getSsoId(HttpServletRequest request) { + String sso_id = null; + sso_id = CookieManager.getCookieValue(SECode.USER_ID, request); + return sso_id; + } + + public String getSystemAccount(String sso_id) { + NXContext context = null; + NXUserAPI userAPI = null; + String retValue = null; + + try { + context = getContext(); + userAPI = new NXUserAPI(context); + System.out.println("##############################################"); + System.out.println("sso_id 2: "+sso_id); + System.out.println("SERVICE_NAME : "+SERVICE_NAME); + System.out.println("##############################################"); + NXAccount account = userAPI.getUserAccount(sso_id, SERVICE_NAME); + System.out.println(account.toString()); + retValue = account.getAccountName(); + System.out.println("retValue = " + retValue); + } catch (Exception e) { + e.printStackTrace(); + } + return retValue; + } + + // 통합 SSO 로그인페이지 이동 + public void goLoginPage(HttpServletResponse response, String uurl) + throws Exception { + com.initech.eam.nls.CookieManager.addCookie(SECode.USER_URL, uurl, SSO_DOMAIN, response); + com.initech.eam.nls.CookieManager.addCookie(SECode.R_TOA, TOA, SSO_DOMAIN, response); + response.sendRedirect(NLS_LOGIN_URL + "?UURL=" + uurl); + } + + // 통합 SSO 로그인페이지 이동 + public void goLoginPage(HttpServletResponse response)throws Exception { + CookieManager.addCookie(SECode.USER_URL, ASCP_URL, SSO_DOMAIN, response); + CookieManager.addCookie(SECode.R_TOA, TOA, SSO_DOMAIN, response); + response.sendRedirect(NLS_LOGIN_URL); + } + + // 통합인증 세션을 체크 하기 위하여 사용되는 API + public String getEamSessionCheck(HttpServletRequest request,HttpServletResponse response){ + String retCode = ""; + try { + retCode = CookieManager.verifyNexessCookie(request, response, 10, COOKIE_SESSTION_TIME_OUT,PROVIDER_LIST); + } catch(Exception npe) { + npe.printStackTrace(); + } + return retCode; + } + + //ND API를 사용해서 쿠키검증하는것(현재 표준에서는 사용안함, 근데 해도 되기는 함) + public String getEamSessionCheck2(HttpServletRequest request,HttpServletResponse response) + { + String retCode = ""; + try { + NXNLSAPI nxNLSAPI = new NXNLSAPI(context); + retCode = nxNLSAPI.readNexessCookie(request, response, 0, 0); + } catch(Exception npe) { + npe.printStackTrace(); + } + return retCode; + } + + // SSO 에러페이지 URL + public void goErrorPage(HttpServletResponse response, int error_code)throws Exception { + CookieManager.removeNexessCookie(SSO_DOMAIN, response); + CookieManager.addCookie(SECode.USER_URL, ASCP_URL, SSO_DOMAIN, response); + response.sendRedirect(NLS_ERROR_URL + "?errorCode=" + error_code); + } + + /** + * 사용자 기본정보 가져오기. + * @param String userid + * @return Properties + */ + public Properties getUserInfos(String userid) throws Exception { + + NXContext context = null; + context = getContext(); + + NXUserAPI userAPI = new NXUserAPI(context); + Properties propt = null; + + if (userid==null || userid.length() <= 0) + return propt; + + try { + NXUserInfo userInfo = userAPI.getUserInfo(userid); + propt = new Properties(); + //System.out.println("userInfo.getName()=["+userInfo.getName()+"]"); + propt.setProperty("USERID", userInfo.getUserId()); + propt.setProperty("EMAIL", userInfo.getEmail()); + propt.setProperty("ENABLE", String.valueOf(userInfo.getEnable())); + propt.setProperty("STARTVALID", userInfo.getStartValid()); + propt.setProperty("ENDVALID", userInfo.getEndValid()); + propt.setProperty("NAME", userInfo.getName()); + propt.setProperty("ENCPASSWD", userInfo.getEncpasswd()); + propt.setProperty("LASTPASSWDCHANGE", userInfo.getLastpasswdchange()); + propt.setProperty("LastLoginIP", userInfo.getLastLoginIp()); + propt.setProperty("LastLoginTime", userInfo.getLastLoginTime()); + propt.setProperty("LastLoginAuthLevel", userInfo.getLastLoginAuthLevel()); + + } catch (EmptyResultException e) { + e.printStackTrace(); + } catch (APIException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return propt; + } + + /** + * 사용자의 기본정보 중에서 입력된 키 값만 가지고 온다. + * @param String userid + * @param String key + * @return String + */ + public String getUserInfo(String userid, String key) throws Exception { + if(userid==null||userid.length()<1) return null; + + String userInfo = ""; + + try{ + Properties propt = getUserInfos(userid); + userInfo = propt.getProperty(key); + } catch (Exception e) { + //throw e; + e.printStackTrace(); + } + return userInfo; + } + + /** + * 사용자 확장정보 조회 + * return : Properties + * 사용자가 존재하지 않거나 확장필드가 없다면 return null + */ + public Properties getUserExFields(String userid) + throws Exception { + NXContext context = getContext(); + NXUserAPI userAPI = new NXUserAPI(context); + Properties prop = null; + NXExternalFieldSet nxefs = null; + + try { + nxefs = userAPI.getUserExternalFields(userid); + prop = new Properties(); + + if (nxefs != null) { + Iterator iter = nxefs.iterator(); + while(iter.hasNext()) { + NXExternalField nxef = (NXExternalField) iter.next(); + if(nxef.getName().equals("PASSINIT")){ + String sPassintVal = (String) nxef.getValue(); + prop.setProperty(nxef.getName(), sPassintVal.equals("") ? "0" : sPassintVal); + } + else + prop.setProperty(nxef.getName(), (String) nxef.getValue()); + //System.out.println("확장 필드 " + nxef.getName() + " : " + nxef.getValue()); + } + } else { + // prop is null + } + } catch (EmptyResultException e) { + // 사용자가 존재하지 않거나 확장필드가 없음 + } catch (APIException e) { + throw e; + //e.printStackTrace(); + } + //System.out.println("getUserExFields:[" + prop + "]"); + return prop; + } + + /** + * 사용자의 특정 확장 필드 정보 조회 + * return : String + * 사용자가 존재하지 않거나 확장필드가 없다면 return null + */ + public String getUserExField(String userid, String exName) + throws Exception { + NXContext context = getContext(); + NXUserAPI userAPI = new NXUserAPI(context); + NXExternalField nxef = null; + String returnValue = null; + + try { + +System.out.println("getUserExField==="+ userid ) ; + nxef = userAPI.getUserExternalField(userid, exName); +System.out.println("nxef==="+ (String) nxef.getValue() ) ; + returnValue = (String) nxef.getValue(); + } catch (EmptyResultException e) { + // 사용자가 존재하지 않거나 확장필드가 없음 + } catch (APIException e) { + throw e; + //e.printStackTrace(); + } catch (IllegalArgumentException e) { + throw e; + //e.printStackTrace(); + } + /* + System.out.println("getUserExField:[" + userid + ":" + + exName + ":" + returnValue+ "]"); + */ + return returnValue; + } + +%> diff --git a/kjb-docs/sso/login_exec.jsp b/kjb-docs/sso/login_exec.jsp new file mode 100644 index 0000000..2d2b41f --- /dev/null +++ b/kjb-docs/sso/login_exec.jsp @@ -0,0 +1,69 @@ +<%@ page language="java" contentType="text/html;charset=EUC-KR" %> +<%@ include file="./config.jsp" %> +<% + + String uurl = null; + String sso_id = null; + String service_id = null; + String ext_info = null; + String retCode = null; + //http://test.kjbank.com:8080/se/login_exec.jsp : 꼭 도메인으로 호출해야 된다. + //1.SSO ID 수신 + sso_id = getSsoId(request); + + // 2. UURL 수신 + uurl = request.getParameter("UURL"); + + if (sso_id == null) { + //uurl 이 없다면, 근데 여기서 uurl 은 뭘 체크하는거지? uurl 에 test.kjbank.com:8080/se/login_exec.jsp 를 넣는구나. + if (uurl == null) uurl = ASCP_URL; + + // 3. SSO 인증 정보가 없으면 통합 로그인 페이지로 이동 + goLoginPage(response, uurl); + return; + } else { + + //4.쿠키 유효성 확인 :0(정상) + retCode = getEamSessionCheck(request,response); + + if(!retCode.equals("0")){ + goErrorPage(response, Integer.parseInt(retCode)); + return; + } + /*********************************************************************************** + * < TO-DO > + * SSO 통합 인증이 완료되었으므로, 업무 시스템 로그인은 건너뛰도록 코드를 수정합니다. + * SSO 에서 제공한 사번은 sso_id 변수에 저장되어 있습니다. + * 시스템에서 필요로 하는 세션 정보를 세팅 코드를 여기에 작성합니다. + ************************************************************************************/ + + //5 확장 정보 조회 + ext_info = getUserExField(sso_id, "CELLPHONENO"); + service_id = SERVICE_NAME; + System.out.println ("SERVICE_NAME : " + service_id); + // + //6.업무시스템에 읽을 사용자 아이디를 세션으로 생성 + /* 변경 전 + String EAM_ID = (String)session.getAttribute("SSO_ID"); + if(EAM_ID == null || EAM_ID.equals("")) { + session.setAttribute("SSO_ID", sso_id); + session.setAttribute("APP_ID", service_id); + session.setAttribute("EXT_INFO", ext_info); + session.setAttribute("RET_CODE", retCode); + } + */ + + + /*변경 후*/ + if(!sso_id.replace(" ", "").equals("")) { + session.setAttribute("SSO_ID", sso_id); + session.setAttribute("APP_ID", service_id); + session.setAttribute("EXT_INFO", ext_info); + session.setAttribute("RET_CODE", retCode); + } + + //7.업무시스템 페이지 호출(세션 페이지 또는 메인페이지 지정) --> 업무시스템에 맞게 URL 수정! + response.sendRedirect(SERVER_URL + ":" + SERVER_PORT + "/se/app01.jsp"); + } +%> + diff --git a/kjb-docs/sso/광주은행_SSO연동가이드.zip b/kjb-docs/sso/광주은행_SSO연동가이드.zip new file mode 100644 index 0000000..6e533e9 Binary files /dev/null and b/kjb-docs/sso/광주은행_SSO연동가이드.zip differ diff --git a/obp.http b/obp.http new file mode 100644 index 0000000..ce3ab3e --- /dev/null +++ b/obp.http @@ -0,0 +1,463 @@ +POST http://192.168.246.15:8181/api/billing/findBillingForCondition +Content-Type: application/json; charset=UTF-8 +x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI= +x-obp-partnercode: 100001-01 + +{ + "partnerCode": "000002-01", + "targetOnMonth": "202503" +} + + +### + + curl -X POST --location "http://192.168.246.15:8181/api/billing/billing/findBillingForCondition" \ + -H "Content-Type: application/json; charset=UTF-8" \ + -H "x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI=" \ + -H "x-obp-partnercode: 100001-01" \ + -d '{ + "partnerCode": "000002-01", + "targetOnMonth": "202510" + }' +POST http://192.168.246.15:8181/api/billing/findBillingForCondition +x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI= +x-obp-partnercode: 100001-01 +Content-Type: application/json; charset=UTF-8 + +{ + "partnerCode": "000002-01", + "targetOnMonth": "202503" +} + +### + + + + +{ + "billingId" : "001-20250401-060001-0001467", + "status" : "01", + "apiTotalValueSum" : 0.00, + "apiSum" : 100000.00, + "apiTotalValueSum2" : 0.00, + "apiSum2" : 100000.00, + "productTotalValueCountSum" : 0, + "productTotalValueAmountSum" : 0.00, + "productSum" : 600000.00, + "productTotalValueCountSum2" : 0, + "productTotalValueAmountSum2" : 0.00, + "productSum2" : 600000.00, + "totalValueCountSum" : 0.00, + "totalValueCountSum2" : 0.00, + "totalValueAmountSum" : 0.00, + "totalValueAmountSum2" : 0.00, + "sum" : 700000.00, + "sum2" : 700000.00, + "list" : [ { + "id" : "001-20250401-060001-0001469", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "[계좌] 실지명의조회", + "paymentTargetType" : "01", + "paymentTargetId" : "001-20180918-085325-0000353", + "detailFeeType" : "11", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "001-20201012-154617-0000969", + "feeDetailId" : "001-20221115-105403-0011851", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 100.00, + "minimumAmount" : 0.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 0.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 100.00, + "minimumAmount2" : 0.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 0.00, + "totalCount2" : 0, + "detailFeeTypeName" : null, + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 0.00, + "sum2" : 0.00 + }, { + "id" : "001-20250401-060001-0001468", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "예금주성명조회 API", + "paymentTargetType" : "01", + "paymentTargetId" : "001-20180419-200001-0000100", + "detailFeeType" : "11", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131549-0085992", + "feeDetailId" : "101-20180516-131549-0085993", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 100.00, + "minimumAmount" : 100000.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 100000.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 100.00, + "minimumAmount2" : 100000.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 100000.00, + "totalCount2" : 0, + "detailFeeTypeName" : null, + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 100000.00, + "sum2" : 100000.00 + }, { + "id" : "001-20250401-060002-0001472", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "P2P 가상계좌(투자자용) API", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000003", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0085994", + "feeDetailId" : "101-20180516-131550-0085995", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 300.00, + "minimumAmount" : 200000.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 200000.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 300.00, + "minimumAmount2" : 200000.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 200000.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 200000.00, + "sum2" : 200000.00 + }, { + "id" : "001-20250401-060002-0001476", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "P2P 지급이체(당행_프로모션) API", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000007", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0085996", + "feeDetailId" : "101-20180516-131550-0085997", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 0.00, + "minimumAmount" : 0.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 0.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 0.00, + "minimumAmount2" : 0.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 0.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 0.00, + "sum2" : 0.00 + }, { + "id" : "001-20250401-060002-0001474", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "P2P 지급이체 API(타행)", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000005", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0085998", + "feeDetailId" : "101-20180516-131550-0085999", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 300.00, + "minimumAmount" : 200000.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 200000.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 300.00, + "minimumAmount2" : 200000.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 200000.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 200000.00, + "sum2" : 200000.00 + }, { + "id" : "001-20250401-060002-0001473", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "P2P 지급이체(대출실행집금용) API", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000004", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0086000", + "feeDetailId" : "101-20180516-131550-0086001", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 50.00, + "minimumAmount" : 0.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 0.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 50.00, + "minimumAmount2" : 0.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 0.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 0.00, + "sum2" : 0.00 + }, { + "id" : "001-20250401-060002-0001475", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "P2P 투자금 관리 API", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000006", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0086002", + "feeDetailId" : "001-20231128-161411-0007408", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "01", + "value" : 0.05, + "minimumAmount" : 200000.00, + "maximumAmount" : null, + "paymentBase" : "02", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 200000.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 0.05, + "minimumAmount2" : 200000.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 200000.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "금액", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "율", + "sum" : 200000.00, + "sum2" : 200000.00 + }, { + "id" : "001-20250401-060001-0001470", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "입금이체(당행) API", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000001", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0086004", + "feeDetailId" : "101-20180516-131550-0086005", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 50.00, + "minimumAmount" : 0.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 0.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 50.00, + "minimumAmount2" : 0.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 0.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 0.00, + "sum2" : 0.00 + }, { + "id" : "001-20250401-060001-0001471", + "billingId" : "001-20250401-060001-0001467", + "paymentTargetName" : "입금이체(타행) API", + "paymentTargetType" : "02", + "paymentTargetId" : "001-20171227-110000-0000002", + "detailFeeType" : "21", + "revision" : 0, + "contractAccountId" : "001-20180430-104631-0798825", + "feeId" : "101-20180516-131550-0086006", + "feeDetailId" : "101-20180516-131550-0086007", + "bank" : "034", + "accountNo" : "1107617597186", + "valueDivision" : "02", + "value" : 300.00, + "minimumAmount" : 0.00, + "maximumAmount" : null, + "paymentBase" : "01", + "paymentBaseValue" : 1.00, + "paymentTiming" : "02", + "totalValue" : 0.00, + "withdrawalAmount" : 0.00, + "totalCount" : 0, + "revision2" : 0, + "totalValue2" : 0.00, + "value2" : 300.00, + "minimumAmount2" : 0.00, + "maximumAmount2" : null, + "changeReason2" : "system billing", + "withdrawalAmount2" : 0.00, + "totalCount2" : 0, + "detailFeeTypeName" : "상품수수료", + "paymentBaseName" : "건", + "paymentTimingName" : "사후부과", + "valueDivisionName" : "금액", + "sum" : 0.00, + "sum2" : 0.00 + } ] +} + + + + + +$ curl -X POST --location "http://192.168.246.15:8181/api/billing/billing/findBillingForCondition/print" -H "Content-Type: application/json; charset=UTF-8" -H "x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI=" -H "x-obp-partnercode: 100001-01" -d '{ + "partnerCode": "000014-01", + "targetOnMonth": "202503" + }' +{ + "billingId" : "001-20250401-060004-0001512", + "status" : "01", + "apiTotalValueSum" : 0, + "apiSum" : 0, + "apiTotalValueSum2" : 0, + "apiSum2" : 0, + "productTotalValueCountSum" : 0, + "productTotalValueAmountSum" : 0, + "productSum" : 0, + "productTotalValueCountSum2" : 0, + "productTotalValueAmountSum2" : 0, + "productSum2" : 0, + "totalValueCountSum" : 0, + "totalValueCountSum2" : 0, + "totalValueAmountSum" : 0, + "totalValueAmountSum2" : 0, + "sum" : 0, + "sum2" : 0, + "list" : [ ] +} + + + + + +curl -X POST --location "http://192.168.246.15:8181/api/billing/billing/findBillingForCondition/print" -H "Content-Type: application/json; charset=UTF-8" -H "x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI=" -H "x-obp-partnercode: 100001-01" -d '{ +> "partnerCode": "000014-01", +> "targetOnMonth": "202503" +> }' +{ + "error" : { + "code" : "E.BILLING.NOT_PRINT_BEFORE_CONFIRM", + "message" : "확정이전에는 출력이 안됩니다." + } +} + + + + + + +weblogic@apigwd01(DEV-eAPIM-GW#1):[/weblogic] +$ curl -X POST --location "http://192.168.246.15:8181/api/billing/billing/findBillingForCondition/print" -H "Content-Type: application/json; charset=UTF-8" -H "x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI=" -H "x-obp-partnercode: 100001-01" -d '{ + "partnerCode": "000002-02", + "targetOnMonth": "201803" + }' +{ + "error" : { + "code" : "E.BILLING.NOT_PRINT_BEFORE_CONFIRM", + "message" : "확정이전에는 출력이 안됩니다." + } +} +weblogic@apigwd01(DEV-eAPIM-GW#1):[/weblogic] +$ curl -X POST --location "http://192.168.246.15:8181/api/billing/billing/findBillingForCondition/print" -H "Content-Type: application/json; charset=UTF-8" -H "x-obp-trust-system: APIMPT-0002-QVBJTVBULTAwMDI=" -H "x-obp-partnercode: 100001-01" -d '{ + "partnerCode": "000002-02", + "targetOnMonth": "199603" + }' +{ + "error" : { + "code" : "E.BILLING.NOT_PRINT_BEFORE_CONFIRM", + "message" : "확정이전에는 출력이 안됩니다." + } +} 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 f848d93..554da7b 100644 --- a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java +++ b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java @@ -3,6 +3,9 @@ package com.eactive.ext.kjb.common; import lombok.Data; import org.hibernate.validator.constraints.NotEmpty; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.Pattern; @@ -29,17 +32,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자리 영문대소문자 및 숫자 조합이어야 합니다.") @@ -69,4 +72,189 @@ public class KjbProperty { @KjbPropertyValue(key = "kjb.ums.timeout", defaultValue = "10") @Min(1) @Max(30) private int umsTimeout = 10; + + + @KjbPropertyValue(key = "kjb.obp.url") + @Pattern(regexp = "^(https?://[^\\s/$.?#][^\\s]*)|\\s*$", message = "kjb.obp.url 는 유효한 URL 형식이거나 공백이어야 합니다. 예: http://example.com/api") + private String obpUrl; + + @KjbPropertyValue(key = "kjb.obp.trust_system.key", defaultValue = "x-obp-trust-system") + @NotEmpty + private String obpTrustSystemKey = "x-obp-trust-system"; + + @KjbPropertyValue(key = "kjb.obp.trust_system.value", defaultValue = "APIMPT-0002-QVBJTVBULTAwMDI=") + @NotEmpty + private String obpTrustSystemValue = "APIMPT-0002-QVBJTVBULTAwMDI="; + + @KjbPropertyValue(key = "kjb.obp.partner_code.key", defaultValue = "x-obp-partnercode") + @NotEmpty + private String obpPartnerCodeKey = "x-obp-partnercode"; + + @KjbPropertyValue(key = "kjb.obp.partner_code.value", defaultValue = "100001-01") + @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 + private String obpUrlCommission = "/api/billing/billing/findBillingForCondition"; + + @KjbPropertyValue(key = "kjb.obp.url.commission_print", defaultValue = "/api/billing/billing/findBillingForCondition/print") + @NotEmpty + private String obpUrlCommissionPrint = "/api/billing/billing/findBillingForCondition/print"; + + @KjbPropertyValue(key = "kjb.obp.url.organization_fmt", defaultValue = "/api/customer/findCustomerByBusinessManRegistrationNo/%s") + @NotEmpty + private String obpUrlOrganizationFmt = "/api/customer/findCustomerByBusinessManRegistrationNo/%s"; + + + // 광주은행 SSO + + @KjbPropertyValue(key = "kjb.url", defaultValue = "https://manage-eapim.kjbank.com") + @NotEmpty + private String url = "https://manage-eapim.kjbank.com"; + + @KjbPropertyValue(key = "kjb.sso.service_name", defaultValue = "Web") + @NotEmpty + private String ssoServiceName = "Web"; + + @KjbPropertyValue(key = "kjb.sso.ascp_url", defaultValue = "/monitoring/sso/login.do") + @NotEmpty + private String ssoAscpUri = "/monitoring/sso/login.do"; + + @KjbPropertyValue(key = "kjb.sso.domain", defaultValue = "sso.kjbank.com") + @NotEmpty + private String ssoDomain = "sso.kjbank.com"; + + @KjbPropertyValue(key = "kjb.sso.nls_port", defaultValue = "13890") + @Min(1024) @Max(65535) + private int ssoNlsPort = 13890; + + @KjbPropertyValue(key = "kjb.sso.ignore_validation", defaultValue = "false") + private boolean ssoIgnoreValidation = false; + + @KjbPropertyValue( + key = "kjb.sso.sync_info", + defaultValue = "true", + description = "SSO 인증시 사용자 정보 동기화 여부(false 시 이름과 ID를 동일하게 처리. 나머지 정보는 무시)") + private boolean ssoSyncInfo = true; + + + // SMS 2차 인증 + + @KjbPropertyValue( + key = "kjb.sms_auth.enabled", + defaultValue = "true", + description = "SMS 2차 인증 사용 여부. true 시 휴대폰번호 없는 사용자는 ID/PW 로그인 불가") + private boolean smsAuthEnabled = false; + + @KjbPropertyValue( + key = "kjb.sms_auth.mode", + defaultValue = "real", + description = "인증번호 생성 모드: real(랜덤), hhmm00(현재시각+00), fixed(고정값)") + @Pattern(regexp = "^(real|hhmm00|fixed)$", message = "kjb.sms_auth.mode는 real, hhmm00, fixed 중 하나여야 합니다.") + private String smsAuthMode = "real"; + + @KjbPropertyValue( + key = "kjb.sms_auth.fixed_value", + defaultValue = "654321", + description = "mode가 fixed일 때 사용할 고정 인증번호 (6자리 숫자)") + @Pattern(regexp = "^\\d{6}$", message = "kjb.sms_auth.fixed_value는 6자리 숫자여야 합니다.") + private String smsAuthFixedValue = "654321"; + + + @KjbPropertyValue( + key = "kjb.api.allow_ip_list", + description = "API 접근 허용 IP 목록 (콤마(,)로 구분된 IP들, 예: 192.168.0.1, 192.168.1.*,127.*.*.*", + defaultValue = "10.15.106.*, 10.14.8.*, 10.15.8.*, 192.168.240.*, 127.0.0.1, 192.168.132.*, 192.168.131.*" + ) + private String apiAllowIpList = "10.15.106.*, 10.14.8.*, 10.15.8.*, 192.168.240.*, 127.0.0.1, 192.168.132.*, 192.168.131.*"; + + + @KjbPropertyValue( + key = "kjb.api_gw_metric.page_size", + description = "API GW Metric 조회시 페이지 크기 설정 (Integer, 0 = 무한, 기본값 10000)", + defaultValue = "10000" + ) + private Integer apiGwMetricPageSize = 10000; + + + public String toJsonString(boolean isPretty) { + return isPretty + ? new GsonBuilder().setPrettyPrinting().create().toJson(this) + : new Gson().toJson(this); + } + + + // 기본 프로퍼티 목록을 콘솔로 출력 (가이드용) + public static void main(String[] args) { + System.out.println("| Key | Default Value | Description |"); + System.out.println("|-----|---------------|-------------|"); + + for (java.lang.reflect.Field field : KjbProperty.class.getDeclaredFields()) { + KjbPropertyValue annotation = field.getAnnotation(KjbPropertyValue.class); + if (annotation != null) { + String key = annotation.key(); + String defaultValue = annotation.defaultValue(); + String description = annotation.description(); + System.out.printf("| %s | %s | %s |%n", key, defaultValue, description); + } + } + } } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbPropertyHolder.java b/src/main/java/com/eactive/ext/kjb/common/KjbPropertyHolder.java new file mode 100644 index 0000000..16acbd5 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/common/KjbPropertyHolder.java @@ -0,0 +1,92 @@ +package com.eactive.ext.kjb.common; + +import lombok.extern.slf4j.Slf4j; + +import java.util.function.Function; + +/** + * KjbProperty 싱글톤 홀더. + * + *

모든 곳에서 {@code KjbPropertyHolder.get()}으로 현재 KjbProperty에 접근합니다. + * eapim-admin에서 초기화 시 {@code initialize()}를 호출하고, + * MonitoringContext.refresh() 시 {@code reload()}를 호출하여 값을 갱신합니다.

+ * + *

사용 예시:

+ *
+ * // 값 접근
+ * String url = KjbPropertyHolder.get().getUmsHostUrl();
+ *
+ * // 초기화 (eapim-admin에서)
+ * KjbPropertyInjector injector = new KjbPropertyInjector(service, "Monitoring");
+ * KjbPropertyHolder.initialize(injector::inject);
+ *
+ * // reload (MonitoringContext.refresh() 시)
+ * KjbPropertyHolder.reload();
+ *
+ * // 테스트용
+ * KjbPropertyHolder.setForTest(mockProperty);
+ * 
+ */ +@Slf4j +public class KjbPropertyHolder { + + private static volatile KjbProperty instance = new KjbProperty(); + private static volatile Function injector; + + private KjbPropertyHolder() { + // 유틸리티 클래스 + } + + /** + * 현재 KjbProperty 인스턴스를 반환합니다. + */ + public static KjbProperty get() { + return instance; + } + + /** + * 초기화 함수를 등록하고 최초 로딩을 수행합니다. + * eapim-admin 부팅 시 MonitoringContextImpl.init()에서 호출됩니다. + * + * @param injectorFn KjbProperty를 DB에서 로딩하는 함수 (KjbPropertyInjector::inject) + */ + public static void initialize(Function injectorFn) { + injector = injectorFn; + reload(); + log.info("KjbPropertyHolder 초기화 완료"); + } + + /** + * DB에서 프로퍼티를 다시 로딩합니다. + * MonitoringContext.refresh() 호출 시 함께 호출됩니다. + */ + public static void reload() { + if (injector != null) { + try { + instance = injector.apply(new KjbProperty()); + log.info("KjbProperty reload 완료"); + } catch (Exception e) { + log.error("KjbProperty reload 실패: {}", e.getMessage(), e); + } + } else { + log.debug("KjbPropertyHolder: injector가 설정되지 않음 (테스트 환경일 수 있음)"); + } + } + + /** + * 테스트용 - 직접 인스턴스를 설정합니다. + */ + public static void setForTest(KjbProperty testProperty) { + instance = testProperty; + log.debug("KjbProperty 테스트 인스턴스 설정"); + } + + /** + * 테스트용 - 기본값으로 초기화합니다. + */ + public static void resetForTest() { + instance = new KjbProperty(); + injector = null; + log.debug("KjbPropertyHolder 테스트 초기화"); + } +} diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbPropertyValue.java b/src/main/java/com/eactive/ext/kjb/common/KjbPropertyValue.java index 0522f7d..6b75459 100644 --- a/src/main/java/com/eactive/ext/kjb/common/KjbPropertyValue.java +++ b/src/main/java/com/eactive/ext/kjb/common/KjbPropertyValue.java @@ -10,4 +10,5 @@ import java.lang.annotation.Target; public @interface KjbPropertyValue { String key(); String defaultValue() default ""; + String description() default ""; } diff --git a/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java b/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java new file mode 100644 index 0000000..21e5aff --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/KjbObpModule.java @@ -0,0 +1,85 @@ +package com.eactive.ext.kjb.obp; + +import com.eactive.ext.kjb.common.KjbObpException; +import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +@Slf4j +public class KjbObpModule { + private static final String FAKE_PARTNER_CODE = "999999-99"; + + private static volatile KjbObpModule instance; + + private KjbObpModule() { + // 싱글톤 + } + + public static KjbObpModule getInstance() { + if (instance == null) { + synchronized (KjbObpModule.class) { + if (instance == null) { + instance = new KjbObpModule(); + } + } + } + return instance; + } + + private static KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } + + private ObpClient getClient() { + return ObpClient.getInstance(); + } + + /** + * 사업자등록번호로 partnerCode 조회 + * @param bizNo 사업자등록번호 (숫자 외 문자 자동 제거) + * @return partnerCode + * @throws KjbObpException 조회 실패 시 + */ + public String queryPartnerCode(String bizNo) throws KjbObpException { + bizNo = ObpUtil.onlyDigit(bizNo); + + if (StringUtils.isEmpty(bizNo)) { + throw new IllegalArgumentException("BizNo is empty."); + } + + if (bizNo.length() != 10) { + throw new IllegalArgumentException("BizNo must be 10 digits. (input: " + bizNo + ")"); + } + + ObpClient client = getClient(); + + // 가상모드: URL 미설정 시 가상 파트너코드 반환 + if (!client.isRealMode()) { + log.info("[OBP] 가상모드 - 가상 partnerCode 반환: {}", FAKE_PARTNER_CODE); + return FAKE_PARTNER_CODE; + } + + String path = String.format(getProperty().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(); + } +} 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..bb6745a --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpClient.java @@ -0,0 +1,178 @@ +package com.eactive.ext.kjb.obp; + +import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; +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 volatile ObpClient instance; + + private ObpClient() { + // 싱글톤 + } + + public static ObpClient getInstance() { + if (instance == null) { + synchronized (ObpClient.class) { + if (instance == null) { + instance = new ObpClient(); + } + } + } + return instance; + } + + private static KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } + + /** + * 실제 모드 여부 (URL이 설정되어 있는지 확인) + */ + public boolean isRealMode() { + return StringUtils.isNotEmpty(getProperty().getObpUrl()); + } + + private RequestConfig createRequestConfig() { + KjbProperty prop = getProperty(); + return RequestConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(prop.getObpConnectionTimeout())) + .setResponseTimeout(Timeout.ofSeconds(prop.getObpResponseTimeout())) + .build(); + } + + /** + * 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 = getProperty().getObpUrl() + path; + HttpGet httpGet = new HttpGet(url); + applyDefaultHeaders(httpGet); + applyAdditionalHeaders(httpGet, additionalHeaders); + httpGet.setConfig(createRequestConfig()); + + 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 = getProperty().getObpUrl() + path; + HttpPost httpPost = new HttpPost(url); + applyDefaultHeaders(httpPost); + applyAdditionalHeaders(httpPost, additionalHeaders); + httpPost.setConfig(createRequestConfig()); + + if (StringUtils.isNotEmpty(jsonBody)) { + httpPost.setEntity(EntityBuilder.create() + .setText(jsonBody) + .setContentType(ContentType.APPLICATION_JSON) + .build()); + } + + return execute(httpPost); + } + + private void applyDefaultHeaders(HttpUriRequestBase request) { + KjbProperty prop = getProperty(); + request.setHeader("Content-Type", "application/json; charset=utf-8"); + request.setHeader(prop.getObpTrustSystemKey(), prop.getObpTrustSystemValue()); + request.setHeader(prop.getObpPartnerCodeKey(), prop.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 (CloseableHttpClient httpClient = HttpClients.createDefault()) { + 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(getProperty().getObpTimeout(), TimeUnit.SECONDS); + + if (log.isDebugEnabled()) { + log.debug("[OBP] 응답: status={}, body={}", response.getStatus(), response.getBody()); + } + + return response; + + } catch (InterruptedException | ExecutionException | TimeoutException | java.io.IOException 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(); + } + } +} 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/ObpResponse.java b/src/main/java/com/eactive/ext/kjb/obp/ObpResponse.java new file mode 100644 index 0000000..ce32e47 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpResponse.java @@ -0,0 +1,13 @@ +package com.eactive.ext.kjb.obp; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class ObpResponse { + private String body; + private Map headers = new HashMap<>(); + private int status; +} diff --git a/src/main/java/com/eactive/ext/kjb/obp/ObpUtil.java b/src/main/java/com/eactive/ext/kjb/obp/ObpUtil.java new file mode 100644 index 0000000..e8cefde --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/obp/ObpUtil.java @@ -0,0 +1,10 @@ +package com.eactive.ext.kjb.obp; + +import java.util.stream.Collectors; + +class ObpUtil { + static String onlyDigit(String str) { + return str == null ? "" : + str.chars().filter(Character::isDigit).mapToObj(c -> String.valueOf((char)c)).collect(Collectors.joining()); + } +} diff --git a/src/main/java/com/eactive/ext/kjb/sso/ExtendedInfo.java b/src/main/java/com/eactive/ext/kjb/sso/ExtendedInfo.java new file mode 100644 index 0000000..686012f --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/sso/ExtendedInfo.java @@ -0,0 +1,38 @@ +package com.eactive.ext.kjb.sso; + +import lombok.Getter; + +public enum ExtendedInfo { + DATEOFBIRTH("생년월일 + 성별"), + CELLPHONENO("핸드폰"), + UPPERTCODE("모점점번코드"), + PARTCODE("소속부점코드"), + LEGACYDEPTCODE("인사부서코드"), + LEGACYDEPTNAME("인사부서명"), + LEGACYPOSITIONCODE("인사직위코드"), + LEGACYPOSITIONNAME("인사직위명"), + LEGACYFUNCTIONCODE("인사직책코드"), + LEGACYFUNCTIONNAME("인사직책명"), + LEGACYROLLCODE("인사직무코드"), + LEGACYROLLNAME("인사직무명"), + LEGACYOFCLEVELCODE("인사직급코드"), + LEGACYOFCLEVELNAME("인사직급명"), + PART_GBN("본부(1), 영업점(2)"), + STATUS("재직(1),휴가(2),퇴직(9)"), + CEF_APPOINTDATE("최종방령일"), + OTHERDEPTCODE("겸직/파견 부서코드"), + OTHERDEPTNAME("겸직/파견 부서명"), + OTHERPOSITIONCODE("겸직/파견 직위코드"), + OTHERFIUNCTIONCODE("겸직/파견 직책코드"), + OTHERROLLCODE("겸직/파견 직무코드"), + OTHEROFCLEVELCODE("겸직/파견 직급코드") + ; + + + @Getter + private final String description; + + ExtendedInfo(String description) { + this.description = description; + } +} diff --git a/src/main/java/com/eactive/ext/kjb/sso/KjbSsoModule.java b/src/main/java/com/eactive/ext/kjb/sso/KjbSsoModule.java new file mode 100644 index 0000000..687c45e --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/sso/KjbSsoModule.java @@ -0,0 +1,350 @@ +package com.eactive.ext.kjb.sso; + +import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; +import com.initech.eam.api.*; +import com.initech.eam.base.APIException; +import com.initech.eam.base.EmptyResultException; +import com.initech.eam.nls.CookieManager; +import com.initech.eam.smartenforcer.SECode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.Vector; + +@Slf4j +public class KjbSsoModule { + private static volatile KjbSsoModule instance; + + private final Vector PROVIDER_LIST = new Vector(); + private final int COOKIE_SESSTION_TIME_OUT = 3000000; + + // 인증 타입 (ID/PW 방식 : 1, 인증서 : 3) + private final String TOA = "1"; + private final String SSO_DOMAIN = ".kjbank.com"; + private final String COOKIE_PADDING = "_V42"; + + private static final int timeout = 15000; + + private KjbSsoModule() { + CookieManager.setEncStatus(true); + SECode.setCookiePadding(COOKIE_PADDING); + } + + public static KjbSsoModule getInstance() { + if (instance == null) { + synchronized (KjbSsoModule.class) { + if (instance == null) { + instance = new KjbSsoModule(); + } + } + } + return instance; + } + + private static KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } + + private String getServiceName() { + return getProperty().getSsoServiceName(); + } + + public String getAscpUrl() { + KjbProperty prop = getProperty(); + return prop.getUrl() + prop.getSsoAscpUri(); + } + + private String getNlsUrl() { + return "http://" + getProperty().getSsoDomain(); + } + + private String getNlsPort() { + return String.valueOf(getProperty().getSsoNlsPort()); + } + + private String getNlsLoginUrl() { + return getNlsUrl() + ":" + getNlsPort() + "/nls3/clientLogin.jsp"; + } + + private String getNlsLogoutUrl() { + return getNlsUrl() + ":" + getNlsPort() + "/nls3/NCLogout.jsp"; + } + + private String getNlsErrorUrl() { + return getNlsUrl() + ":" + getNlsPort() + "/nls3/error.jsp"; + } + + private String getNdUrl1() { + return "http://" + getProperty().getSsoDomain() + ":5480"; + } + + public Vector getProviderList() { + Vector list = new Vector<>(); + list.add(getProperty().getSsoDomain()); + return list; + } + + + @SuppressWarnings("unchecked") + public NXContext getContext() { + NXContext context = null; + try { + List serverurlList = new ArrayList<>(); + serverurlList.add(getNdUrl1()); + context = new NXContext(serverurlList); + CookieManager.setEncStatus(true); + SECode.setCookiePadding(COOKIE_PADDING); + } + catch (Exception e) + { + log.warn("getContext", e); + } + return context; + } + + + /** + * 통합 SSO ID 조회 + * @param request + * @return + */ + public String getSsoId(HttpServletRequest request) { + String sso_id = null; + sso_id = CookieManager.getCookieValue(SECode.USER_ID, request); + return sso_id; + } + + public String getSystemAccount(String sso_id) { + NXContext context = null; + NXUserAPI userAPI = null; + String retValue = null; + + try { + context = getContext(); + userAPI = new NXUserAPI(context); + String serviceName = getServiceName(); + log.info("##############################################"); + log.info("sso_id 2: "+sso_id); + log.info("SERVICE_NAME : "+ serviceName); + log.info("##############################################"); + NXAccount account = userAPI.getUserAccount(sso_id, serviceName); + log.info(account.toString()); + retValue = account.getAccountName(); + log.info("retValue = " + retValue); + } catch (Exception e) { + log.error("KjbSsoModule Exception", e); + } + return retValue; + } + + /** + * 통합 SSO 로그인페이지 이동 + * @param response + * @param uurl + * @throws Exception + */ + public void goLoginPage(HttpServletResponse response, String uurl) + throws Exception { + CookieManager.addCookie(SECode.USER_URL, uurl, SSO_DOMAIN, response); + CookieManager.addCookie(SECode.R_TOA, TOA, SSO_DOMAIN, response); + response.sendRedirect(getNlsLoginUrl() + "?UURL=" + uurl); + } + + /** + * 통합 SSO 로그인페이지 이동 + * @param response + * @throws Exception + */ + public void goLoginPage(HttpServletResponse response) throws Exception { + CookieManager.addCookie(SECode.USER_URL, getAscpUrl(), SSO_DOMAIN, response); + CookieManager.addCookie(SECode.R_TOA, TOA, SSO_DOMAIN, response); + response.sendRedirect(getNlsLoginUrl()); + } + + /** + * 통합인증 세션을 체크 하기 위하여 사용되는 API + * @param request + * @param response + * @return + */ + public String getEamSessionCheck(HttpServletRequest request, HttpServletResponse response) { + String retCode = ""; + try { + retCode = CookieManager.verifyNexessCookie(request, response, 10, COOKIE_SESSTION_TIME_OUT, getProviderList()); + } catch(Exception npe) { + log.warn("getEamSessionCheck", npe); + } + return retCode; + } + + /** + * ND API를 사용해서 쿠키검증하는것(현재 표준에서는 사용안함, 근데 해도 되기는 함) + * @param request + * @param response + * @return + */ + public String getEamSessionCheck2(HttpServletRequest request, HttpServletResponse response) { + String retCode = ""; + try { + NXContext ctx = getContext(); + NXNLSAPI nxNLSAPI = new NXNLSAPI(ctx); + retCode = nxNLSAPI.readNexessCookie(request, response, 0, 0); + } catch(Exception npe) { + log.error("KjbSsoModule-getEamSessionCheck2 Exception", npe); + } + return retCode; + } + + /** + * SSO 에러페이지 URL + * @param response + * @param error_code + * @throws Exception + */ + public void goErrorPage(HttpServletResponse response, int error_code) throws Exception { + CookieManager.removeNexessCookie(SSO_DOMAIN, response); + CookieManager.addCookie(SECode.USER_URL, getAscpUrl(), SSO_DOMAIN, response); + response.sendRedirect(getNlsErrorUrl() + "?errorCode=" + error_code); + } + + /** + * 사용자 기본정보 가져오기. + * @param userid + * @return Properties + */ + public Properties getUserInfos(String userid) throws Exception { + NXContext context = null; + context = getContext(); + + NXUserAPI userAPI = new NXUserAPI(context); + Properties propt = null; + + if (userid==null || userid.length() <= 0) + return propt; + + try { + NXUserInfo userInfo = userAPI.getUserInfo(userid); + propt = new Properties(); + propt.setProperty("USERID", userInfo.getUserId()); + propt.setProperty("EMAIL", userInfo.getEmail()); + propt.setProperty("ENABLE", String.valueOf(userInfo.getEnable())); + propt.setProperty("STARTVALID", userInfo.getStartValid()); + propt.setProperty("ENDVALID", userInfo.getEndValid()); + propt.setProperty("NAME", userInfo.getName()); + propt.setProperty("ENCPASSWD", userInfo.getEncpasswd()); + propt.setProperty("LASTPASSWDCHANGE", userInfo.getLastpasswdchange()); + propt.setProperty("LastLoginIP", userInfo.getLastLoginIp()); + propt.setProperty("LastLoginTime", userInfo.getLastLoginTime()); + propt.setProperty("LastLoginAuthLevel", userInfo.getLastLoginAuthLevel()); + + } catch (Exception e) { + log.error("KjbSsoModule-getUserInfos Exception", e); + throw e; + } + + return propt; + } + + /** + * 사용자의 기본정보 중에서 입력된 키 값만 가지고 온다. + * @param userid + * @param key + * @return + * @throws Exception + */ + public String getUserInfo(String userid, String key) throws Exception { + if(userid==null|| userid.isEmpty()) return null; + + String userInfo = ""; + + try { + Properties propt = getUserInfos(userid); + userInfo = propt.getProperty(key); + } catch (Exception e) { + log.error("KjbSsoModule-getUserInfos Exception", e); + throw e; + } + return userInfo; + } + + /** + * 사용자 확장정보 조회 + * return : Properties + * 사용자가 존재하지 않거나 확장필드가 없다면 return null + */ + @SuppressWarnings("rawtypes") + public Properties getUserExFields(String userid) throws Exception { + NXContext context = getContext(); + NXUserAPI userAPI = new NXUserAPI(context); + Properties prop = null; + NXExternalFieldSet nxefs = null; + + try { + nxefs = userAPI.getUserExternalFields(userid); + prop = new Properties(); + + if (nxefs != null) { + Iterator iter = nxefs.iterator(); + while(iter.hasNext()) { + NXExternalField nxef = (NXExternalField) iter.next(); + if(nxef.getName().equals("PASSINIT")){ + String sPassintVal = (String) nxef.getValue(); + prop.setProperty(nxef.getName(), sPassintVal.equals("") ? "0" : sPassintVal); + } + else + prop.setProperty(nxef.getName(), (String) nxef.getValue()); + //System.out.println("확장 필드 " + nxef.getName() + " : " + nxef.getValue()); + } + } else { + // prop is null + log.debug("KjbSsoModule-getUserExFields property is null"); + } + } catch (EmptyResultException e) { + // 사용자가 존재하지 않거나 확장필드가 없음 + log.warn("사용자가 존재하지 않거나 확장필드가 없음", e); + } catch (APIException e) { + log.warn("SSO-APIException", e); + throw e; + //e.printStackTrace(); + } + //System.out.println("getUserExFields:[" + prop + "]"); + return prop; + } + + /** + * 사용자의 특정 확장 필드 정보 조회 + * return : String + * 사용자가 존재하지 않거나 확장필드가 없다면 return null + */ + public String getUserExField(String userid, String exName) + throws Exception { + NXContext context = getContext(); + NXUserAPI userAPI = new NXUserAPI(context); + NXExternalField nxef = null; + String returnValue = null; + + try { + log.debug("KjbSsoModule-getUserExField userid : {}", userid); + nxef = userAPI.getUserExternalField(userid, exName); + log.debug("KjbSsoModule-getUserExField nxef === {}", nxef.getValue()); + returnValue = (String) nxef.getValue(); + } catch (EmptyResultException e) { + // 사용자가 존재하지 않거나 확장필드가 없음 + log.warn("사용자가 존재하지 않거나 확장필드가 없음", e); + } catch (Exception e) { + log.error("KjbSsoModule-getUserExField Exception", e); + throw e; + } + + return returnValue; + } +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java b/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java index 7a38cde..07a7b09 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java @@ -8,9 +8,10 @@ import com.eactive.ext.kjb.common.KjbProperty; import com.eactive.ext.kjb.eai.EaiBatchInterface; import com.eactive.ext.kjb.eai.EaiBatchMessage; import com.eactive.ext.kjb.eai.KjbEaiBatchModule; -import com.eactive.ext.kjb.safedb.KjbSafedbWrapper; +//import com.eactive.ext.kjb.safedb.KjbSafedbWrapper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -50,7 +51,8 @@ public class EmailJob { this.messageRequest = messageRequest; this.prop = prop; - String filename = messageRequest.getServiceId() + "_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(now) + ".dat"; +// String filename = messageRequest.getServiceId() + "_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(now) + ".dat"; + String filename = messageRequest.getServiceId() + "_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(now) + ".dat"; this.eaiFilePath = Paths.get(prop.getUmsEaiBatchSendDir(), filename); this.backupFilePath = Paths.get(prop.getUmsEaiBatchBackupDir(), filename); this.failedFilePath = Paths.get(prop.getUmsEaiBatchBackupDir(), filename + ".failed"); @@ -228,30 +230,39 @@ public class EmailJob { public String generateJSON(boolean isPretty) { validateMessageRequest(messageRequest, true, true); - KjbSafedbWrapper safedb = KjbSafedbWrapper.getInstance(); +// KjbSafedbWrapper safedb = KjbSafedbWrapper.getInstance(); JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("MAKE_DATE", DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now())); jsonObject.addProperty("MAKE_SEQNO", DateTimeFormatter.ofPattern("HHmmssSSS").format(LocalDateTime.now())); jsonObject.addProperty("CAMP_ID", messageRequest.getServiceId()); // 전자금융 고객이메일 발송 업무ID (SERVICE_ID) jsonObject.addProperty("ID", messageRequest.getUmsUid()); // 고객 ID - jsonObject.addProperty("EMAIL", safedb.encryptNotRnnoString(messageRequest.getEmail())); +// jsonObject.addProperty("EMAIL", safedb.encryptNotRnnoString(messageRequest.getEmail())); + jsonObject.addProperty("EMAIL", messageRequest.getEmail()); jsonObject.addProperty("NAME", messageRequest.getUsername()); jsonObject.addProperty("ENCKEY", ""); // 보안메일용으로 기입 불필요 Gson gson = new Gson(); - log.debug("Template string to json: {}", messageRequest.getMessage()); - JsonObject contentJSon = gson.fromJson(messageRequest.getMessage(), JsonObject.class); - contentJSon.entrySet().forEach(entry -> { - jsonObject.add(entry.getKey(), entry.getValue()); - }); + String message = messageRequest.getMessage(); + JsonArray jsonArray = new JsonArray(); + log.debug("Template string to json: {}", message); + if (StringUtils.isNotEmpty(message)) { + JsonObject contentJSon = gson.fromJson(message, JsonObject.class); + contentJSon.entrySet().forEach(entry -> { + jsonObject.add(entry.getKey(), entry.getValue()); + }); + } + + jsonArray.add(jsonObject); // jsonObject.addProperty("SUBJECT", messageRequest.getSubject()); // jsonObject.addProperty("MESSAGE", messageRequest.getMessage()); String jsonString; if (isPretty) { - jsonString = new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject); + // pretty print 제외 +// jsonString = new GsonBuilder().setPrettyPrinting().create().toJson(jsonArray); + jsonString = new GsonBuilder().create().toJson(jsonArray); } else { jsonString = jsonObject.toString(); } diff --git a/src/main/java/com/eactive/ext/kjb/ums/KjbEmailSendModule.java b/src/main/java/com/eactive/ext/kjb/ums/KjbEmailSendModule.java index 159f554..ce83a77 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/KjbEmailSendModule.java +++ b/src/main/java/com/eactive/ext/kjb/ums/KjbEmailSendModule.java @@ -5,6 +5,7 @@ import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.apim.portal.template.repository.MessageRequestRepository; import com.eactive.ext.kjb.common.KjbEaiBatchException; import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; import com.eactive.ext.kjb.utils.ValidationUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -18,24 +19,35 @@ import java.util.function.BiConsumer; public class KjbEmailSendModule { public final static int[] ALLOW_CHANNELS = { MessageCode.Channels.EMAIL.getValue() }; - private static KjbEmailSendModule instance = null; - private static KjbProperty prop; + private static volatile KjbEmailSendModule instance; + private MessageRequestRepository repo; - private final MessageRequestRepository repo; - - - public static synchronized KjbEmailSendModule getInstance(KjbProperty property, - MessageRequestRepository messageRequestRepository) { - - ValidationUtil.validateOrThrow(property); + private KjbEmailSendModule() { + // 싱글톤 + } + public static KjbEmailSendModule getInstance() { if (instance == null) { - instance = new KjbEmailSendModule(property, messageRequestRepository); + synchronized (KjbEmailSendModule.class) { + if (instance == null) { + instance = new KjbEmailSendModule(); + } + } } - return instance; } + /** + * MessageRequestRepository 설정 (eapim-admin 초기화 시 호출) + */ + public void setRepository(MessageRequestRepository repository) { + this.repo = repository; + } + + private static KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } + public static boolean isAllowChannel(MessageRequest message) { for (int channel : ALLOW_CHANNELS) { if ((message.getMessageCode().getChannels() & channel) == channel) { @@ -47,6 +59,8 @@ public class KjbEmailSendModule { public static void environmentCheck() { + KjbProperty prop = getProperty(); + if (StringUtils.isEmpty(prop.getEaiBatchUrl())) { log.warn("EAI_BATCH_URL 가 설정되지 않음. 실제 EAI 배치 연동 없이 동작함"); } @@ -101,24 +115,13 @@ public class KjbEmailSendModule { log.info("KjbEmailSendModule - Environment check passed."); } - - - private KjbEmailSendModule(KjbProperty property, - MessageRequestRepository repo) { - this.repo = repo; - - if (property != null && prop == null) { - prop = property; - } - - environmentCheck(); - } - public void sendEmail(MessageRequest messageRequest) throws KjbEaiBatchException { String serviceId = "UNKNOWN"; - serviceId = messageRequest.getMessageCode().getServiceId(); - messageRequest.setServiceId(serviceId); + if (StringUtils.isEmpty(messageRequest.getServiceId())) { + serviceId = messageRequest.getMessageCode().getServiceId(); + messageRequest.setServiceId(serviceId); + } if (StringUtils.isEmpty(serviceId)) { throw new IllegalArgumentException("MessageRequest MessageCode serviceId is required"); @@ -133,7 +136,7 @@ public class KjbEmailSendModule { repo.save(messageRequest); } - EmailJob job = EmailJob.create(prop, messageRequest, repo); + EmailJob job = EmailJob.create(getProperty(), messageRequest, repo); job.sendEmail(); log.debug("JSON : {}", job.generateJSON(true)); 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..883675f 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java +++ b/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java @@ -3,13 +3,13 @@ package com.eactive.ext.kjb.ums; import com.eactive.apim.portal.template.entity.MessageCode; import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; import com.eactive.ext.kjb.common.KjbUmsException; -import com.eactive.ext.kjb.ums.gson.IUmsGsonObject; -import com.eactive.ext.kjb.ums.gson.UmsRequestPayload; -import com.eactive.ext.kjb.ums.gson.VerifyPhoneUmsTemplate; +import com.eactive.ext.kjb.ums.gson.*; import com.eactive.ext.kjb.utils.JsonUtil; import com.eactive.ext.kjb.utils.ValidationUtil; import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -39,34 +39,42 @@ public class KjbUmsService { private final static String CONTENT_TYPE = "application/json; charset=utf-8"; private final static String AUTHORIZATION_FMT = "Bearer %s"; - private final KjbProperty property; - private final boolean isRealMode; - private final CloseableHttpClient httpClient; - private final RequestConfig requestConfig; + private static volatile KjbUmsService instance; + private KjbUmsService() { + // 싱글톤 + } - public KjbUmsService(KjbProperty property) { - if (property == null) { - log.error("[KJB-UMS] 파라미터 설정 되지 않음"); - throw new IllegalArgumentException("property is null"); + public static KjbUmsService getInstance() { + if (instance == null) { + synchronized (KjbUmsService.class) { + if (instance == null) { + instance = new KjbUmsService(); + } + } } + return instance; + } - isRealMode = property.getUmsHostUrl() != null && StringUtils.isNotEmpty(property.getUmsHostUrl().trim()); + private KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } - if (isRealMode) { - ValidationUtil.validateOrThrow(property); + private boolean isRealMode() { + String url = getProperty().getUmsHostUrl(); + return url != null && StringUtils.isNotEmpty(url.trim()); + } - httpClient = HttpClients.createDefault(); - requestConfig = RequestConfig.custom() - .setConnectTimeout(Timeout.ofSeconds(property.getEaiBatchConnectionTimeout())) - .setResponseTimeout(Timeout.ofSeconds(property.getEaiBatchResponseTimeout())) - .build(); - } else { - httpClient = HttpClients.createDefault(); - requestConfig = null; - } + private CloseableHttpClient createHttpClient() { + return HttpClients.createDefault(); + } - this.property = property; + private RequestConfig createRequestConfig() { + KjbProperty prop = getProperty(); + return RequestConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(prop.getUmsConnectionTimeout())) + .setResponseTimeout(Timeout.ofSeconds(prop.getUmsResponseTimeout())) + .build(); } @@ -90,13 +98,10 @@ public class KjbUmsService { throw new IllegalArgumentException("지원하지 않는 발송 채널 - " + messageRequest.getMessageCode().toString()); } - IUmsGsonObject content = null; - content = VerifyPhoneUmsTemplate.builder().authNumber(messageRequest.getMessage()).build(); - String umsMessageId = this.sendKakaoAlimTalk(messageRequest.getId(), UmsBizWorkCode.VERIFY_PHONE, messageRequest.getPhone(), - content + GsonUtil.toJsonObject(messageRequest.getMessage()) ); log.info("MessageRequest sent - {}", umsMessageId); @@ -104,7 +109,13 @@ public class KjbUmsService { return true; } - public String sendKakaoAlimTalk(String guid, UmsBizWorkCode bizWorkCode, String cpno, IUmsGsonObject content) throws KjbUmsException { + public String sendObpMonitoring(String cpno, String message) throws KjbUmsException { + log.info("OBP 모니터링 용 메세지 발송 - message: {}", message); + + return this.sendKakaoAlimTalk(null, UmsBizWorkCode.MONITORING, cpno, GsonUtil.toJsonObject(ObpMonitoringUmsTemplate.builder().message(message).build())); + } + + public String sendKakaoAlimTalk(String guid, UmsBizWorkCode bizWorkCode, String cpno, JsonObject content) throws KjbUmsException { if (content == null) { throw new IllegalArgumentException("content is null"); } @@ -137,13 +148,14 @@ public class KjbUmsService { } private UmsCallResult call(String url, UmsRequestPayload requestPayload) throws KjbUmsException { + KjbProperty prop = getProperty(); ExecutorService executor = Executors.newSingleThreadExecutor(); Gson gson = new Gson(); String json = gson.toJson(requestPayload); - HttpPost httpPost = new HttpPost(property.getUmsHostUrl() + url); + HttpPost httpPost = new HttpPost(prop.getUmsHostUrl() + url); httpPost.setHeader("Content-Type", CONTENT_TYPE); - httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, property.getUmsApiKey())); + httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, prop.getUmsApiKey())); httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build()); if (log.isDebugEnabled()) { @@ -156,52 +168,54 @@ public class KjbUmsService { } } - httpPost.setConfig(requestConfig); + httpPost.setConfig(createRequestConfig()); - if (isRealMode) { - Future future = executor.submit(() -> { - return httpClient.execute(httpPost, res -> { - try { - UmsCallResult result; - String body = EntityUtils.toString(res.getEntity()); - if (log.isDebugEnabled()) { - log.debug("Response - {}", body); - } - result = gson.fromJson(body, UmsCallResult.class); - result.setUmsMessageId(requestPayload.getMessageIdentityNo()); - result.setSuccess(true); - return result; - } catch (JsonSyntaxException e) { - log.error("UMS Call error(JSON Syntax)", e); - return UmsCallResult.builder() - .umsMessageId(requestPayload.getMessageIdentityNo()) - .isSuccess(false) - .throwable(e) - .build(); - } catch (Exception e) { - log.error("UMS Call error", e); - return UmsCallResult.builder() - .umsMessageId(requestPayload.getMessageIdentityNo()) - .isSuccess(false) - .throwable(e) - .build(); - } finally { - log.debug("Response status - {}", res.getCode()); + if (isRealMode()) { + try (CloseableHttpClient httpClient = createHttpClient()) { + Future future = executor.submit(() -> { + return httpClient.execute(httpPost, res -> { + try { + UmsCallResult result; + String body = EntityUtils.toString(res.getEntity()); + if (log.isDebugEnabled()) { + log.debug("Response - {}", body); + } + result = gson.fromJson(body, UmsCallResult.class); + result.setUmsMessageId(requestPayload.getMessageIdentityNo()); + result.setSuccess(true); + return result; + } catch (JsonSyntaxException e) { + log.error("UMS Call error(JSON Syntax)", e); + return UmsCallResult.builder() + .umsMessageId(requestPayload.getMessageIdentityNo()) + .isSuccess(false) + .throwable(e) + .build(); + } catch (Exception e) { + log.error("UMS Call error", e); + return UmsCallResult.builder() + .umsMessageId(requestPayload.getMessageIdentityNo()) + .isSuccess(false) + .throwable(e) + .build(); + } finally { + log.debug("Response status - {}", res.getCode()); - if (res.getCode() != 200) { - Arrays.asList(res.getHeaders()).forEach(o -> { - log.warn("[Header] {}: {}", o.getName(), o.getValue()); - }); + if (res.getCode() != 200) { + Arrays.asList(res.getHeaders()).forEach(o -> { + log.warn("[Header] {}: {}", o.getName(), o.getValue()); + }); + } } - } + }); }); - }); - try { - return future.get(property.getUmsTimeout(), TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - log.error("EAI Batch call exception", e); - throw new KjbUmsException("EAI Ums call failed", e); + return future.get(prop.getUmsTimeout(), TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException | IOException e) { + log.error("UMS call exception", e); + throw new KjbUmsException("UMS call failed", e); + } finally { + executor.shutdown(); } } else { log.warn("UMS URL 미지정으로 실제 호출을 진행하지 않음."); diff --git a/src/main/java/com/eactive/ext/kjb/ums/gson/GsonUtil.java b/src/main/java/com/eactive/ext/kjb/ums/gson/GsonUtil.java new file mode 100644 index 0000000..3731e47 --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/ums/gson/GsonUtil.java @@ -0,0 +1,131 @@ +package com.eactive.ext.kjb.ums.gson; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; + +public class GsonUtil { + /** 한 번만 생성해 두고 재사용 */ + private static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() // 필요 시 포맷 옵션 + .create(); + + private GsonUtil() { + /* 인스턴스 생성 금지 */ + } + + /* ------------------------------------------------------------------ * + * 1. 객체 ↔ JSON 문자열 + * ------------------------------------------------------------------ */ + /** 객체 → JSON 문자열 */ + public static String toJson(Object src) { + return GSON.toJson(src); + } + + /** JSON 문자열 → 객체 */ + public static T fromJson(String json, Class clazz) { + return GSON.fromJson(json, clazz); + } + + /* ------------------------------------------------------------------ * + * 2. 객체 ↔ JsonElement + * ------------------------------------------------------------------ */ + /** 객체 → JsonElement (Gson 내부 표현) */ + public static JsonElement toJsonElement(Object src) { + return GSON.toJsonTree(src); + } + + /** JsonElement → 객체 */ + public static T fromJsonElement(JsonElement json, Class clazz) { + return GSON.fromJson(json, clazz); + } + + /* ------------------------------------------------------------------ * + * 3. JSON 문자열 ↔ JsonObject + * ------------------------------------------------------------------ */ + /** JSON 문자열 → JsonObject (정상적인 객체만 허용) */ + public static JsonObject fromJsonObject(String json) { + JsonElement elem = GSON.fromJson(json, JsonElement.class); + if (!elem.isJsonObject()) { + throw new IllegalArgumentException("JSON 문자열이 JsonObject 가 아닙니다: " + json); + } + return elem.getAsJsonObject(); + } + + /** JsonObject → JSON 문자열 (정렬 옵션 없이) */ + public static String toJsonString(JsonObject jsonObj) { + return GSON.toJson(jsonObj); + } + + /* ------------------------------------------------------------------ * + * 4. JSON 문자열 ↔ List + * ------------------------------------------------------------------ */ + /** JSON 문자열 → List<T> */ + public static List fromJsonList(String json, Class clazz) { + // ParameterizedType 으로 List 를 명시 + Type listType = new ParameterizedType() { + @Override public Type[] getActualTypeArguments() { return new Type[]{clazz}; } + @Override public Type getRawType() { return List.class; } + @Override public Type getOwnerType() { return null; } + }; + return GSON.fromJson(json, listType); + } + + /* ------------------------------------------------------------------ * + * 5. 기타 (예시: JsonArray ↔ List) + * ------------------------------------------------------------------ */ + /** JsonArray → List<T> */ + public static List fromJsonArray(JsonArray array, Class clazz) { + return GSON.fromJson(array, new TypeToken>(){}.getType()); + } + + /** List<T> → JsonArray */ + public static JsonArray toJsonArray(List list) { + return GSON.toJsonTree(list).getAsJsonArray(); + } + + /** + * JSON 문자열을 JsonObject 로 변환 + * + * @param json 유효한 JSON 객체 문자열 (예: {"name":"John","age":30}) + * @return JsonObject 인스턴스 + * @throws IllegalArgumentException JSON 문자열이 객체가 아닐 경우 + */ + public static JsonObject toJsonObject(String json) { + // 먼저 JsonElement 로 파싱 + JsonElement element = GSON.fromJson(json, JsonElement.class); + + // 객체인지 검사 + if (!element.isJsonObject()) { + throw new IllegalArgumentException( + "Provided JSON string is not a JsonObject: " + json); + } + + // JsonObject 로 캐스팅 후 반환 + return element.getAsJsonObject(); + } + + /** + * 일반 Java 객체 → JsonObject + * + * @param src 변환할 객체 (Map, POJO 등) + * @return JsonObject 인스턴스 + * @throws IllegalArgumentException 객체가 JsonObject 로 변환될 수 없을 때 + */ + public static JsonObject toJsonObject(Object src) { + // 객체를 JsonElement 로 변환 + JsonElement element = GSON.toJsonTree(src); + + // 객체인지 검사 (Map, POJO 등은 JsonObject 가 됨) + if (!element.isJsonObject()) { + throw new IllegalArgumentException( + "Provided object cannot be represented as a JsonObject: " + + src.getClass().getName()); + } + + return element.getAsJsonObject(); + } +} diff --git a/src/main/java/com/eactive/ext/kjb/ums/gson/ObpMonitoringUmsTemplate.java b/src/main/java/com/eactive/ext/kjb/ums/gson/ObpMonitoringUmsTemplate.java index b384155..57d0f9f 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/gson/ObpMonitoringUmsTemplate.java +++ b/src/main/java/com/eactive/ext/kjb/ums/gson/ObpMonitoringUmsTemplate.java @@ -1,8 +1,12 @@ package com.eactive.ext.kjb.ums.gson; import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Data; +@Data +@Builder public class ObpMonitoringUmsTemplate implements IUmsGsonObject { - @SerializedName("AUTH_NO") - private String authNumber; + @SerializedName("MOTR_EVAL_CNTN") + private String message; } diff --git a/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java b/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java index 9dade0e..0bc99b1 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java +++ b/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java @@ -1,5 +1,7 @@ package com.eactive.ext.kjb.ums.gson; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import lombok.Builder; import lombok.Data; @@ -34,7 +36,7 @@ public class UmsRequestPayload { // MSG_BZWK_PARAM_CTNT object 메시지업무파라미터내용 4000 메시지업무코드의 템플릿에서 사용하는 변수와 변수값 @NotNull @SerializedName("MSG_BZWK_PARAM_CTNT") - private IUmsGsonObject content; + private JsonObject content; // 발송예약일시 / 14 / 실 발송 일자(yyyyMMddHHmmss) // SND_RESR_DTTM string 발송예약일시 14 20240418111500 O 실 발송 일자(현재시간) diff --git a/src/main/java/com/eactive/ext/kjb/utils/KjbPropertyUtil.java b/src/main/java/com/eactive/ext/kjb/utils/KjbPropertyUtil.java index 39f836a..4fb4359 100644 --- a/src/main/java/com/eactive/ext/kjb/utils/KjbPropertyUtil.java +++ b/src/main/java/com/eactive/ext/kjb/utils/KjbPropertyUtil.java @@ -11,11 +11,16 @@ public class KjbPropertyUtil { Class clazz = KjbProperty.class; StringBuilder sb = new StringBuilder(); + StringBuilder sbDelete = new StringBuilder(); for (Field field : clazz.getDeclaredFields()) { KjbPropertyValue anno = field.getAnnotation(KjbPropertyValue.class); if (anno != null) { + sbDelete.append("DELETE FROM EMSADM.TSEAIRM24 WHERE PRPTYGROUPNAME = 'Monitoring' AND PRPTYNAME = '"); + sbDelete.append(anno.key()); + sbDelete.append("';\n"); + sb.append("insert into EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) values ('Monitoring', "); String key = anno.key(); @@ -27,6 +32,9 @@ public class KjbPropertyUtil { } System.out.println(sb); + System.out.println(); + System.out.println(); + System.out.println(sbDelete); } private static String addQuote(String str) { 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 테스트 완료"); + } +} 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 5015ef9..d0cfe69 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 @@ -4,6 +4,7 @@ import com.eactive.apim.portal.template.entity.MessageCode; import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.ext.kjb.common.KjbEaiBatchException; import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; import com.eactive.ext.kjb.common.KjbUmsException; import com.eactive.ext.kjb.ums.KjbEmailSendModule; import com.eactive.ext.kjb.ums.KjbUmsService; @@ -14,6 +15,8 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @@ -23,14 +26,16 @@ public class UmsTest { @BeforeAll public static void init() { - prop = new KjbProperty(); + 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"); if (OsUtil.isWindows()) { + prop.setEaiBatchUrl(""); + prop.setUmsHostUrl(""); prop.setUmsEaiBatchSendDir("c:\\kjb-logs\\sendmail\\snd"); prop.setUmsEaiBatchBackupDir("c:\\kjb-logs\\sendmail\\bak"); prop.setUmsEaiBatchErrorDir("c:\\kjb-logs\\sendmail\\error"); @@ -41,6 +46,8 @@ public class UmsTest { prop.setUmsEaiBatchBackupDir("/Data/eapim/portal/sendmail/bak"); prop.setUmsEaiBatchErrorDir("/Data/eapim/portal/sendmail/error"); } + + KjbPropertyHolder.setForTest(prop); } @Test @@ -58,7 +65,7 @@ public class UmsTest { messageRequest.setSubject("제목 - test"); messageRequest.setMessage("{\"ATHN_NO\"; \"369369\"}"); - KjbUmsService umsService = new KjbUmsService(prop); + KjbUmsService umsService = KjbUmsService.getInstance(); try { boolean result = umsService.send(messageRequest); @@ -71,6 +78,9 @@ public class UmsTest { log.info("done"); } + /** + * :eapim-admin-kjb:test --tests "com.eactive.ext.kjb.ums.test.UmsTest.testEmail" + */ @Test public void testEmail() { Assumptions.assumeTrue( @@ -82,25 +92,71 @@ public class UmsTest { log.info("{}: {}", key, System.getProperty((String) key)); }); - KjbEmailSendModule service = KjbEmailSendModule.getInstance(prop, null); - MessageRequest messageRequest = new MessageRequest(); - messageRequest.setId(UUID.randomUUID().toString()); - messageRequest.setMessageCode(MessageCode.USER_VERIFICATION_EMAIL); - messageRequest.setEmail("dearmai@dearmai.net"); - messageRequest.setUmsUid(UUID.randomUUID().toString()); - messageRequest.setUsername("김광은"); - messageRequest.setSubject("제목 - test"); - messageRequest.setMessage("{\"ATHN_NO\": \"543216\"}"); - messageRequest.setServiceId("FM_APIM001"); + KjbEmailSendModule service = KjbEmailSendModule.getInstance(); + + List requestList = new ArrayList<>(); + + + log.info("#1 요청 생성 - 이메일 인증"); + requestList.add(createMessageRequest(MessageCode.USER_VERIFICATION_EMAIL, "{\"ATHN_NO\": \"543216\"}")); + + log.info("#2 요청 생성 - 비밀번호 초기화"); + requestList.add(createMessageRequest(MessageCode.USER_PASSWORD_RESET, "{\"ATHN_NO\": \"new-password\"}")); + + log.info("#3 요청 생성 - 비밀번호 변경 완료"); + requestList.add(createMessageRequest(MessageCode.USER_PASSWORD_CHANGED, "")); + + log.info("#4 요청 생성 - 로그인 제한"); + requestList.add(createMessageRequest(MessageCode.USER_ACCOUNT_LOCKED, "{\"RJCT_RSN\": \"reason\"}")); + + log.info("#5 요청 생성 - 이메일 초대"); + requestList.add(createMessageRequest(MessageCode.USER_INVITATION, + "{\"CRPT_NM\":\"corpName\",\"MNGR_NM\":\"managerName\",\"CUST_ID\":\"loginId\",\"RCMD_CD\":\"321654\",\"URL_ADDR\":\"https://www.naver.com/\"}")); + + log.info("#6 요청 생성 - 이메일 초대 취소"); + requestList.add(createMessageRequest(MessageCode.USER_INVITATION_CANCELED, + "{\"CRPT_NM\":\"corpName\",\"MNGR_NM\":\"managerName\"}")); + + log.info("#7 요청 생성 - 법인관리자 및 법인 등록 승인"); + requestList.add(createMessageRequest(MessageCode.MANAGER_WITH_ORG_REGISTER_APPROVED, "")); + + log.info("#8 요청 생성 - 법인관리자 및 법인 등록 거절"); + requestList.add(createMessageRequest(MessageCode.MANAGER_WITH_ORG_REGISTER_REJECTED, "{\"RJCT_RSN\": \"reason\"}")); + + log.info("#9 요청 생성 - 앱 승인"); + requestList.add(createMessageRequest(MessageCode.APP_REGISTER_APPROVED, "{\"APP_IDNT_NM\": \"appName\"}")); + + log.info("#10 요청 생성 - 앱 승인 거절"); + requestList.add(createMessageRequest(MessageCode.APP_REGISTER_REJECTED, "{\"RJCT_RSN\": \"reason\"}")); + - log.info("MessageRequest - {}", messageRequest); try { - service.sendEmail(messageRequest); + for (int i = 0; i < requestList.size(); i++) { + MessageRequest request = requestList.get(i); + log.info("#{} - 메일 전송 시도 - {}", i + 1, request.getServiceId()); + service.sendEmail(request); + } } catch (KjbEaiBatchException e) { throw new RuntimeException(e); } log.info("done"); } + + private MessageRequest createMessageRequest(MessageCode messageCode, String message) { + MessageRequest messageRequest = new MessageRequest(); + messageRequest.setId(UUID.randomUUID().toString()); + messageRequest.setMessageCode(messageCode); + messageRequest.setEmail(System.getenv().getOrDefault("KJB_TEST_EMAIL", "25W0064@kjbank.com")); + messageRequest.setUmsUid(UUID.randomUUID().toString()); + messageRequest.setUsername("김광은"); + messageRequest.setSubject("제목 - test"); + messageRequest.setMessage(message); +// messageRequest.setServiceId(serviceId); + + log.info("MessageRequest - {}", messageRequest); + + return messageRequest; + } }