Merge remote-tracking branch 'origin/jenkins_with_weblogic' of C:/KJB_DEV/eapim-bundle/bundles/251218/eapim-admin_incremental_2025-12-01.bundle into jenkins_with_weblogic

This commit is contained in:
Rinjae
2025-12-18 14:32:49 +09:00
6 changed files with 543 additions and 33 deletions
+89 -32
View File
@@ -91,11 +91,13 @@ kjb-gradle.sh tasks --all
1. **IDE 설정 파일 생성 (최초 1회)**
```bash
# IntelliJ IDEA 사용 시
kjb-gradle.sh idea
# Eclipse 사용 시
# Eclipse 사용 시 (기본)
kjb-gradle.sh eclipse
# IntelliJ IDEA 사용 시 (build.gradle.intellij 파일 사용 필요)
# 1. build.gradle을 build.gradle.eclipse로 백업
# 2. build.gradle.intellij를 build.gradle로 복사
# 3. kjb-gradle.sh idea 실행
```
2. **QueryDSL 등 소스 코드 생성**
@@ -109,7 +111,8 @@ kjb-gradle.sh tasks --all
```
4. **IDE에서 프로젝트 열기**
- `kjb-gradle.sh idea` 또는 `kjb-gradle.sh eclipse` 실행 후, IDE에서 프로젝트를 엽니다.
- Eclipse: `kjb-gradle.sh eclipse` 실행 후, IDE에서 프로젝트를 엽니다.
- IntelliJ: IntelliJ 전용 `build.gradle.intellij`를 `build.gradle`로 교체 후 사용합니다.
5. **실행 구성(Run Configuration) 설정**
- 사용하는 WAS(Tomcat 등)에 `eapim-admin` 프로젝트를 배포하도록 설정합니다.
@@ -156,26 +159,24 @@ eapim-admin (root project)
- **Lombok**: 애노테이션 프로세싱을 통한 Getters/setters/builders
- **MapStruct**: DTO와 엔티티 간의 객체 매퍼
#### 이중 Q-Class 생성 (Gradle + Eclipse)
#### Q-Class 생성 위치
이 프로젝트는 **Gradle과 Eclipse 애노테이션 프로세서 모두를 사용**하므로, Q-classes가 **두 위치에 생성**됩니다:
1. **`build/generated/java/`** - `kjb-gradle.sh compileJava` 또는 `kjb-gradle.sh build` 실행 시 Gradle이 생성
2. **`WebContent/generated/`** - Eclipse IDE에서 빌드 시 Eclipse APT가 생성
**이는 예상된 동작이며 의도적입니다.** 두 디렉토리 모두 `.gitignore`에 포함되어 있으며 커밋해서는 안 됩니다.
Q-classes는 `build/generated/java/` 디렉토리에 생성됩니다.
- `kjb-gradle.sh compileJava` 또는 `kjb-gradle.sh build` 실행 시 Gradle이 자동으로 생성
- 이 디렉토리는 `.gitignore`에 포함되어 있으며 커밋해서는 안 됩니다
#### 생성된 코드 작업하기
- **Gradle 빌드**: `build/generated/java/`의 Q-classes가 자동으로 classpath에 포함됩니다
- **Eclipse IDE**: `WebContent/generated/`의 Q-classes가 자동으로 classpath에 포함됩니다
- **업데이트 pull 후**: `kjb-gradle.sh compileJava`를 실행하거나 Eclipse 프로젝트를 새로고침하여 Q-classes를 재생성합니다
- **Q-classes IDE 오류 시**: `kjb-gradle.sh clean compileJava` 또는 Eclipse → Project → Clean으로 재생성합니다
- **업데이트 pull 후**: `kjb-gradle.sh compileJava`를 실행하여 Q-classes를 재생성합니다
- **Q-classes IDE 오류 시**: `kjb-gradle.sh clean compileJava` 재생성합니다
중복이 문제를 일으키지 않는 이유:
- 두 도구 모두 동일한 JPA 엔티티에서 동일한 Q-classes를 생성합니다
- 두 출력 디렉토리 모두 gitignore됩니다
- 각 도구는 자체 생성 클래스를 사용합니다 (classpath 충돌 없음)
#### IDE별 빌드 설정 파일
- **`build.gradle`**: Eclipse 전용 (기본, Eclipse APT 플러그인 포함)
- **`build.gradle.intellij`**: IntelliJ IDEA 전용
IntelliJ를 사용하려면 `build.gradle.intellij`를 `build.gradle`로 교체해야 합니다.
## 로컬 개발 환경 설정
@@ -315,6 +316,50 @@ com.eactive.eai
리포지토리는 Spring Data JPA 규칙을 사용하며, 서비스는 `@Service`, 컨트롤러는 `@RestController`/`@Controller`를 사용합니다.
### Multi-tenancy (동적 스키마 전환)
애플리케이션은 Hibernate Multi-tenancy를 사용하여 **런타임에 스키마를 동적으로 전환**합니다.
**핵심 컴포넌트:**
- `DataSourceContextHolder` - ThreadLocal로 현재 데이터소스 타입 관리
- `DataSourceTypeManager` - 데이터소스 타입 상수 정의 (MONITORING, APIGW, EAI, BAT 등)
- `TenantIdentifierResolver` - `DataSourceContextHolder`에서 tenant 식별자 반환
- `ConfigurableMultiTenantConnectionProvider` - tenant에 따라 `connection.setSchema()` 호출
- `DataSourceTypeInterceptor` - 요청 파라미터 `serviceType`에서 데이터소스 자동 설정
**동작 흐름:**
1. `DataSourceTypeInterceptor.preHandle()` → `request.getParameter("serviceType")` 읽음
2. `DataSourceContextHolder.setDataSourceType(dataSourceType)` 설정
3. JPA 쿼리 실행 시 `TenantIdentifierResolver` → `DataSourceContextHolder`에서 tenant 반환
4. `ConfigurableMultiTenantConnectionProvider` → `connection.setSchema(schema)` 호출
**사용 방법:**
1. **URL 파라미터 방식** (인터셉터가 자동 처리):
```
/api/endpoint?serviceType=APIGW
```
2. **Controller에서 명시적 설정** (특정 데이터소스 강제):
```java
@Controller
public class MyController {
@RequestMapping("/my/api.json")
public String myApi() {
// 서비스 호출 전에 데이터소스 설정
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
return myService.getData();
}
}
```
**주의사항:**
- `@PersistenceContext`만 사용 (기본 `entityManagerFactory` 사용, Multi-tenancy 적용됨)
- `@PersistenceContext(unitName = "entityManagerFactoryForEMS")`는 **Multi-tenancy 미적용** (MONITORING 스키마 고정)
- Controller에서 `DataSourceContextHolder` 설정 후 Service 메서드를 호출해야 트랜잭션 시작 시점에 올바른 스키마가 적용됨
### 주요 초기화
- `AppInitializer` - 시스템 프로퍼티를 설정하는 메인 애플리케이션 시작 빈
- `CustomizingAppInitializer` - 고객사별 초기화
@@ -328,19 +373,7 @@ com.eactive.eai
## IDE 설정
### Eclipse
```bash
kjb-gradle.sh eclipse
```
다음을 생성합니다:
- `.project` 및 `.classpath` 파일
- Eclipse WTP 설정 (context path: `/monitoring`)
- UTF-8 인코딩 설정
- QueryDSL/Lombok을 위한 애노테이션 프로세서 설정
### IntelliJ IDEA
### IntelliJ IDEA (권장)
```bash
kjb-gradle.sh idea
@@ -348,6 +381,30 @@ kjb-gradle.sh idea
또는 IntelliJ에서 직접 "Import Gradle Project"를 사용합니다.
기본 `build.gradle`은 IntelliJ 전용으로 설정되어 있습니다.
### Eclipse
Eclipse를 사용하려면 Eclipse APT 플러그인이 포함된 별도의 빌드 설정 파일을 사용해야 합니다:
```bash
# 1. 기존 build.gradle 백업 (필요시)
cp build.gradle build.gradle.intellij
# 2. Eclipse 전용 설정으로 교체
cp build.gradle.eclipse build.gradle
# 3. Eclipse 프로젝트 생성
kjb-gradle.sh eclipse
```
Eclipse 전용 설정(`build.gradle.eclipse`)은 다음을 포함합니다:
- `com.diffplug.eclipse.apt` 플러그인 (Eclipse APT 지원)
- `.project` 및 `.classpath` 파일 생성
- Eclipse WTP 설정 (context path: `/monitoring`)
- UTF-8 인코딩 설정
- QueryDSL/Lombok을 위한 애노테이션 프로세서 설정
## 테스트
테스트에 사용되는 것들:
+244
View File
@@ -0,0 +1,244 @@
plugins {
id 'java-library'
id 'idea'
id 'war'
id 'project-report'
id 'checkstyle'
}
group 'com.eactive'
version '4.5.7'
def springVersion = "5.3.27"
def quartzVersion = "2.2.1"
def queryDslVersion = "5.0.0"
def hibernateVersion = "5.6.15.Final"
/*def nexusUrl = "https://nexus.eactive.synology.me:8090"*/
//def useOnJboss = false
def generatedJavaDir = "$buildDir/generated/java"
/*repositories {
maven {
url "${nexusUrl}/repository/maven-public/"
allowInsecureProtocol = true
}
}*/
project.webAppDirName = 'WebContent'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
checkstyle {
toolVersion = '8.45.1'
}
sourceSets {
main {
java {
srcDirs generatedJavaDir
}
}
}
compileJava {
options.encoding = 'UTF-8'
options.compilerArgs = [
'-parameters',
'-Aquerydsl.generatedAnnotationClass=com.querydsl.core.annotations.Generated'
]
options.generatedSourceOutputDirectory = project.file(generatedJavaDir)
}
war {
def profile = project.findProperty("profile") ?: "tomcat"
if (profile == "weblogic") {
webXml = file("WebContent/WEB-INF/weblogic-web.xml")
exclude 'WEB-INF/web.xml'
}
// rootSpec.exclude '**/persistence.xml'
exclude '**/context.xml'
archiveFileName = "eapim-admin.war"
duplicatesStrategy = DuplicatesStrategy.WARN
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
options.compilerArgs += ['-parameters', "-Xlint:unchecked", "-Xlint:deprecation"]
}
dependencies {
annotationProcessor "org.projectlombok:lombok:1.18.28", "org.projectlombok:lombok-mapstruct-binding:0.2.0", "org.mapstruct:mapstruct-processor:1.5.5.Final"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jpa", "jakarta.persistence:jakarta.persistence-api:2.2.3", "jakarta.annotation:jakarta.annotation-api:1.3.5"
implementation project(':elink-online-core')
implementation project(':elink-online-transformer')
implementation project(':elink-online-common')
implementation project(':elink-online-emsclient')
implementation project(':elink-portal-common')
implementation project(':kjb-safedb')
implementation project(":eapim-admin-kjb")
/* Custom Libs */
implementation fileTree(dir: 'libs', include: ['*.jar'])
//implementation "org.dom4j:dom4j:2.1.3"
//implementation "com.rabbitmq:amqp-client:3.6.6"
implementation "commons-primitives:commons-primitives:1.0"
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation "commons-lang:commons-lang:2.6"
implementation "commons-httpclient:commons-httpclient:3.1"
implementation "commons-cli:commons-cli:1.1"
implementation "commons-pool:commons-pool:1.6"
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
implementation 'net.sf.j8583:j8583:1.18.0'
implementation "commons-beanutils:commons-beanutils:1.6.1"
implementation 'org.apache.commons:commons-compress:1.26.0'
// implementation "log4j:log4j:1.2.17"
implementation "net.sf.ehcache:ehcache:2.10.5"
implementation "org.apache.poi:poi:3.2-FINAL"
implementation 'org.codehaus.groovy:groovy:3.0.18'
// https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.1'
//implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1'
implementation "javax.jms:javax.jms-api:2.0"
implementation "org.snmp4j:snmp4j:1.10.1"
implementation "com.googlecode.json-simple:json-simple:1.1.1"
implementation "io.netty:netty-all:4.1.0.Final"
compileOnly "org.apache.mina:mina-filter-ssl:1.1.6"
compileOnly "org.apache.mina:mina-core:1.1.6"
// https://mvnrepository.com/artifact/org.modelmapper/modelmapper
implementation 'org.modelmapper:modelmapper:2.4.5'
////implementation "xalan:xalan:2.6.0"
implementation "com.sun.xml.messaging.saaj:saaj-impl:1.3.28"
implementation "org.apache.ibatis:ibatis-sqlmap:2.3.4.726"
implementation "org.jdom:jdom:1.1"
implementation "com.google.code.gson:gson:2.3.1"
implementation "javax.annotation:javax.annotation-api:1.2"
implementation "net.sf.json-lib:json-lib-ext-spring:1.0.2"
implementation "taglibs:standard:1.1.2"
implementation "javax.servlet:jstl:1.1.2"
implementation group: 'com.jcraft', name: 'jsch', version: '0.1.51'
implementation "org.quartz-scheduler:quartz:${quartzVersion}"
// https://mvnrepository.com/artifact/org.apache.poi/poi
implementation group: 'org.apache.poi', name: 'poi', version: '3.17'
// https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml
implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17'
implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.7'
implementation group: 'org.aspectj', name: 'aspectjtools', version: '1.9.7'
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13'
implementation group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.4.15'
implementation group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.5.13'
//implementation "com.eactive:ojdbc8:1.0"
//implementation "com.eactive:INISAFECore:v2.2.28"
//implementation "com.eactive:backport-util-concurrent:1.0"
implementation "org.springframework:spring-web:${springVersion}"
implementation "org.springframework:spring-aop:${springVersion}"
implementation "org.springframework:spring-aspects:${springVersion}"
implementation "org.springframework:spring-orm:${springVersion}"
//implementation "com.eactive:spring-orm-ibatis-4.1.4:1.0"
implementation "org.springframework:spring-webmvc:${springVersion}"
implementation "org.springframework:spring-context:${springVersion}"
implementation "org.springframework:spring-context-support:${springVersion}"
implementation "org.springframework:spring-jdbc:${springVersion}"
implementation 'org.springframework.data:spring-data-jpa:2.5.2'
implementation 'org.hibernate:hibernate-entitymanager:5.4.33.Final'
implementation 'org.hibernate:hibernate-validator:5.4.3.Final'
implementation 'org.hibernate:hibernate-ehcache:5.4.3.Final'
implementation 'org.slf4j:jul-to-slf4j:1.7.35'
implementation 'org.slf4j:jcl-over-slf4j:1.7.35'
implementation 'org.slf4j:log4j-over-slf4j:1.7.35'
implementation 'org.slf4j:slf4j-api:1.7.35'
implementation 'ch.qos.logback:logback-classic:1.2.10'
implementation 'ch.qos.logback:logback-access:1.2.10'
implementation 'ch.qos.logback:logback-core:1.2.10'
implementation files("libs/eai-bap-client.jar")
compileOnly 'javax.xml.bind:jaxb-api:2.3.1'
//implementation 'org.postgresql:postgresql:42.2.23'
implementation 'backport-util-concurrent:backport-util-concurrent:3.0'
implementation ('software.amazon.awssdk:s3:2.20.142') {
exclude group: 'org.slf4j', module: 'slf4j-api'
exclude group: 'org.slf4j', module: 'logback-classic'
}
implementation 'software.amazon.awssdk:sso:2.20.142'
implementation 'software.amazon.awssdk:sts:2.20.142'
implementation ('io.kubernetes:client-java:18.0.1') {
exclude group: 'org.slf4j', module: 'slf4j-api'
exclude group: 'org.slf4j', module: 'logback-classic'
}
implementation group: 'commons-net', name: 'commons-net', version: '3.5'
testRuntimeOnly 'com.h2database:h2:2.1.214'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.15'
}
test {
useJUnitPlatform()
}
configurations.all {
exclude group: 'log4j', module: 'log4j'
exclude group: 'org.apache.activemq'
exclude group: 'org.codehaus.jackson'
resolutionStrategy {
// 10 minute cache of dynamic version navigation
cacheDynamicVersionsFor 10, 'minutes'
// Do not cache changing modules
cacheChangingModulesFor 0, 'seconds'
}
}
task initDirs() {
file(generatedJavaDir).mkdirs()
}
tasks.withType(Checkstyle) {
reports {
xml.required = false
html.required = true
}
}
+6 -1
View File
@@ -4,4 +4,9 @@ GET {{emsUrl}}/kjb/gw-metrics/after-timeslice/2025-12-02_01:00:00.json
### Pretty Print
GET {{emsUrl}}/kjb/gw-metrics/after-timeslice/2025-12-02_01:00:00.json?pretty=true
GET {{emsUrl}}/kjb/gw-metrics/after-timeslice/2025-12-02_01:00:00.json?pretty=true
### API 목록
GET {{emsUrl}}/kjb/apis.json
@@ -0,0 +1,69 @@
package com.eactive.eai.rms.onl.apim.obp;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.onl.apim.common.BaseRestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/**
* OBP API 목록 조회 REST Controller
*
* <p>TSEAIHE01과 TSEAIHS04를 조인하여 API 정보를 JSON으로 제공</p>
*
* @see BaseRestController 세션 체크 우회 및 JSON 유틸리티 제공
*/
@Controller
public class ObpApiController extends BaseRestController {
private static final Logger log = LoggerFactory.getLogger(ObpApiController.class);
private final ObpApiService obpApiService;
public ObpApiController(ObpApiService obpApiService) {
this.obpApiService = obpApiService;
}
/**
* 전체 API 목록 조회
*
* @param pretty true면 JSON 들여쓰기 적용
* @return JSON Array
*/
@RequestMapping(
value = "/kjb/apis.json",
produces = JSON_CONTENT_TYPE
)
@ResponseBody
public String getApis(
@RequestParam(value = "pretty", required = false) Boolean pretty) {
log.debug("OBP API 목록 조회 요청: pretty={}", pretty);
long startTime = System.currentTimeMillis();
// APIGW 데이터소스 강제 설정
DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
try {
List<ObpApiDTO> apis = obpApiService.findAllApis();
String json = toJson(apis, pretty);
long elapsed = System.currentTimeMillis() - startTime;
log.info("OBP API 목록 조회 완료: count={}, size={}, elapsed={}ms",
apis.size(), getFormattedJsonSize(json), elapsed);
return json;
} catch (Exception e) {
log.error("OBP API 목록 조회 실패", e);
throw e;
}
}
}
@@ -0,0 +1,44 @@
package com.eactive.eai.rms.onl.apim.obp;
import lombok.Builder;
import lombok.Getter;
/**
* OBP API 목록 조회 DTO
*
* <p>TSEAIHE01과 TSEAIHS04 테이블을 조인하여 API 정보를 제공</p>
*/
@Getter
@Builder
public class ObpApiDTO {
/**
* API ID (TSEAIHE01.EAISVCNAME)
*/
private final String id;
/**
* API 이름 (TSEAIHE01.EAISVCDESC)
*/
private final String name;
/**
* 활성화 여부 (TSEAIHE01.USEYN = '1' -> true)
*/
private final boolean enabled;
/**
* API URL (TSEAIHS04.APIFULLPATH에서 '|' 이후 부분)
*/
private final String url;
/**
* HTTP Method (TSEAIHS04.APIFULLPATH에서 '|' 이전 부분)
*/
private final String method;
/**
* 인증 타입 (TSEAIHE01.AUTHTYPE)
*/
private final String authType;
}
@@ -0,0 +1,91 @@
package com.eactive.eai.rms.onl.apim.obp;
import com.eactive.eai.data.entity.onl.message.QEAIMessageEntity;
import com.eactive.eai.data.entity.onl.stdmessage.QStandardMessageInfo;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.stream.Collectors;
/**
* OBP API 목록 조회 Service
*
* <p>APIGW 데이터소스의 TSEAIHE01, TSEAIHS04 테이블에서 API 정보 조회</p>
*/
@Service
public class ObpApiService {
@PersistenceContext
private EntityManager entityManager;
private JPAQueryFactory getQueryFactory() {
return new JPAQueryFactory(entityManager);
}
/**
* 전체 API 목록 조회
*
* @return API 목록
*/
public List<ObpApiDTO> findAllApis() {
QEAIMessageEntity he01 = QEAIMessageEntity.eAIMessageEntity;
QStandardMessageInfo hs04 = QStandardMessageInfo.standardMessageInfo;
List<Tuple> results = getQueryFactory()
.select(
he01.eaisvcname,
he01.eaisvcdesc,
he01.useyn,
he01.authtype,
hs04.apifullpath
)
.from(he01)
.leftJoin(hs04).on(he01.eaisvcname.eq(hs04.eaisvcname))
.orderBy(he01.eaisvcname.asc())
.fetch();
return results.stream()
.map(this::toDto)
.collect(Collectors.toList());
}
/**
* Tuple을 DTO로 변환
*/
private ObpApiDTO toDto(Tuple tuple) {
QEAIMessageEntity he01 = QEAIMessageEntity.eAIMessageEntity;
QStandardMessageInfo hs04 = QStandardMessageInfo.standardMessageInfo;
String eaisvcname = tuple.get(he01.eaisvcname);
String eaisvcdesc = tuple.get(he01.eaisvcdesc);
String useyn = tuple.get(he01.useyn);
String authtype = tuple.get(he01.authtype);
String apifullpath = tuple.get(hs04.apifullpath);
// APIFULLPATH 파싱: "POST|/mapi/kjbank/..." -> method="POST", url="/mapi/kjbank/..."
String method = null;
String url = null;
if (StringUtils.isNotBlank(apifullpath) && apifullpath.contains("|")) {
int pipeIndex = apifullpath.indexOf('|');
method = apifullpath.substring(0, pipeIndex);
url = apifullpath.substring(pipeIndex + 1);
}
// USEYN이 "1"이면 enabled = true
boolean enabled = "1".equals(useyn);
return ObpApiDTO.builder()
.id(eaisvcname)
.name(eaisvcdesc)
.enabled(enabled)
.url(url)
.method(method)
.authType(authtype)
.build();
}
}