From 7d4ce8c1c49fbd33c03e0899288155e52915034b Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 16 Dec 2025 17:19:39 +0900 Subject: [PATCH 1/3] =?UTF-8?q?OBP=20GwMetric=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EC=9A=A9=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onl/apim/obp/ObpGwMetricService.java | 7 ++ .../rms/onl/apim/obp/ObpGwMetricAfterDTO.java | 96 +++++++++++++++++++ .../onl/apim/obp/ObpGwMetricController.java | 46 +++++++++ .../onl/apim/obp/ObpGwMetricManService.java | 14 +++ .../eai/apim/obp/ObpGwMetric-oracle.xml | 2 +- 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java index be973ab..52d0dea 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java @@ -43,6 +43,13 @@ public class ObpGwMetricService { return repository.findByTimesliceBetween(startTime, endTime); } + /** + * 특정 시간대 이후의 모든 GwMetric 데이터 조회 + */ + public List findByTimesliceGreaterThanEqual(LocalDateTime timeslice) { + return repository.findByTimesliceGreaterThanEqual(timeslice); + } + /** * 엔티티 저장 (INSERT 또는 UPDATE) */ diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java new file mode 100644 index 0000000..2852cf6 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricAfterDTO.java @@ -0,0 +1,96 @@ +package com.eactive.eai.rms.onl.apim.obp; + +import com.eactive.apim.portal.obp.entity.ObpGwMetric; +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * ObpGwMetric JSON 응답 DTO (after-timeslice 엔드포인트용) + * + *

예시 출력 형식에 맞춤:

+ *
+ * {
+ *   "apiId": "1ba56b82-a9c9-41e2-b3ab-ff9377667d56",
+ *   "apiName": "[해외송금] 가상계좌 입금실패 정보조회",
+ *   "apiRoutingUri": "/api/obs/remittance/remittancefail*",
+ *   "orgId": "ORG001",
+ *   "partnerCode": "000008-01",
+ *   "appId": "APP001",
+ *   "appKey": "l7xx2fd7efc523794be5a1b126873a5de613",
+ *   "requestUri": "-1",
+ *   "timeslice": "2025-12-02T03:00:00.000+0000",
+ *   "attemptedCount": 3,
+ *   "completedCount": 3
+ * }
+ * 
+ */ +@Data +public class ObpGwMetricAfterDTO { + + private static final DateTimeFormatter ISO8601_MILLIS_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'+0000'"); + + @SerializedName("apiId") + private String apiId; + + @SerializedName("apiName") + private String apiName; + + @SerializedName("apiRoutingUri") + private String apiRoutingUri; + + @SerializedName("orgId") + private String orgId; + + @SerializedName("partnerCode") + private String partnerCode; + + @SerializedName("appId") + private String appId; + + @SerializedName("appKey") + private String appKey; + + @SerializedName("requestUri") + private String requestUri; + + @SerializedName("timeslice") + private String timeslice; + + @SerializedName("attemptedCount") + private Long attemptedCount; + + @SerializedName("completedCount") + private Long completedCount; + + /** + * Entity → DTO 변환 + */ + public static ObpGwMetricAfterDTO from(ObpGwMetric entity) { + ObpGwMetricAfterDTO dto = new ObpGwMetricAfterDTO(); + dto.setApiId(entity.getApiId()); + dto.setApiName(entity.getApiName()); + dto.setApiRoutingUri(entity.getUri()); + dto.setOrgId(entity.getOrgId()); + dto.setPartnerCode(entity.getPartnerCode()); + dto.setAppId(entity.getAppId()); + dto.setAppKey(entity.getClientId()); + dto.setRequestUri(entity.getRequestUri()); + dto.setTimeslice(formatTimeslice(entity.getTimeslice())); + dto.setAttemptedCount(entity.getAttemptedCount()); + dto.setCompletedCount(entity.getCompletedCount()); + return dto; + } + + /** + * timeslice를 ISO8601 형식 (밀리초 포함) + UTC 시간대로 변환 + * 예: 2025-12-02T03:00:00.000+0000 + */ + private static String formatTimeslice(LocalDateTime timeslice) { + if (timeslice == null) return null; + return timeslice.format(ISO8601_MILLIS_FORMATTER); + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java index fb3168b..df92b9b 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java @@ -81,6 +81,52 @@ public class ObpGwMetricController implements InterceptorSkipController { } } + /** + * 특정 시간대 이후의 GwMetric 데이터 조회 + * + * @param datetime 조회 시작 시간대 (형식: yyyy-MM-dd_HH:mm:ss) + * @return JSON Array (해당 시간대 이후의 모든 지표) + */ + @RequestMapping( + value = "/kjb/gw-metrics/after-timeslice/{datetime}", + produces = "application/json;charset=utf-8" + ) + @ResponseBody + public String getGwMetricsAfterTimeslice(@PathVariable("datetime") String datetime) { + log.debug("GwMetric after-timeslice 조회 요청: datetime={}", datetime); + long startTime = System.currentTimeMillis(); + + try { + // Path 파라미터 파싱 + LocalDateTime targetTime = LocalDateTime.parse(datetime, PATH_FORMATTER); + LocalDateTime timeslice = targetTime.withMinute(0).withSecond(0).withNano(0); + + // 조회 + List metrics = obpGwMetricManService.findByTimesliceAfter(timeslice); + + // JSON 변환 (GSON) + Gson gson = new GsonBuilder() + .serializeNulls() + .create(); + + String json = gson.toJson(metrics); + + // 응답 크기 계산 (UTF-8 기준) + int jsonBytes = json.getBytes(StandardCharsets.UTF_8).length; + String jsonSize = formatByteSize(jsonBytes); + + long elapsed = System.currentTimeMillis() - startTime; + log.info("GwMetric after-timeslice 조회 완료: datetime={}, count={}, size={}, elapsed={}ms", + datetime, metrics.size(), jsonSize, elapsed); + + return json; + + } catch (Exception e) { + log.error("GwMetric after-timeslice 조회 실패: datetime={}", datetime, e); + throw e; + } + } + /** * 바이트 크기를 읽기 쉬운 형식으로 변환 * 예: 1024 -> "1.0 KB", 1048576 -> "1.0 MB" diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java index 0675162..86c6167 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricManService.java @@ -279,4 +279,18 @@ public class ObpGwMetricManService extends BaseService { .map(ObpGwMetricDTO::from) .collect(Collectors.toList()); } + + /** + * 특정 시간대 이후의 GwMetric 데이터 조회 (REST API용) + * after-timeslice 엔드포인트에서 사용 + */ + public List findByTimesliceAfter(LocalDateTime timeslice) { + DataSourceContextHolder.setDataSourceType( + DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING)); + + List entities = obpGwMetricService.findByTimesliceGreaterThanEqual(timeslice); + return entities.stream() + .map(ObpGwMetricAfterDTO::from) + .collect(Collectors.toList()); + } } diff --git a/src/main/resources/com/eactive/eai/apim/obp/ObpGwMetric-oracle.xml b/src/main/resources/com/eactive/eai/apim/obp/ObpGwMetric-oracle.xml index b187769..05d5b0d 100644 --- a/src/main/resources/com/eactive/eai/apim/obp/ObpGwMetric-oracle.xml +++ b/src/main/resources/com/eactive/eai/apim/obp/ObpGwMetric-oracle.xml @@ -44,7 +44,7 @@ SUM(CASE WHEN LOGPRCSSSERNO = '400' AND (RSPNSERRCDNAME IS NULL OR RSPNSERRCDNAME LIKE 'ESB%') THEN 1 ELSE 0 END) AS COMPLETED_COUNT, - MAX(EAISVCSERNO) AS EAI_SVC_SERNO + MAX(TRIM(EAISVCSERNO)) AS EAI_SVC_SERNO FROM $schemaId$.$tableName$ WHERE EAIBZWKDSTCD = #bzwkCode# AND MSGDPSTYMS BETWEEN #startTime# AND #endTime# From 914c9047e6e48500fcea8fb55e9c35124d1260db Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 16 Dec 2025 17:31:07 +0900 Subject: [PATCH 2/3] =?UTF-8?q?obp=20gw=20metric=20repository=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onl/apim/obp/ObpGwMetricRepository.java | 39 ------------------- .../onl/apim/obp/ObpGwMetricService.java | 1 + 2 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricRepository.java diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricRepository.java deleted file mode 100644 index 58c5031..0000000 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.eactive.eai.rms.data.entity.onl.apim.obp; - -import com.eactive.apim.portal.obp.entity.ObpGwMetric; -import com.eactive.eai.data.jpa.BaseRepository; -import com.eactive.eai.rms.data.EMSDataSource; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -/** - * ObpGwMetric Repository (eapim-admin 위치) - * API Gateway 시간별 거래량 지표 조회/저장을 위한 Spring Data JPA Repository - */ -@EMSDataSource -public interface ObpGwMetricRepository extends BaseRepository { - - /** - * UPSERT 판단을 위한 기존 데이터 조회 - * 동일한 timeslice + apiId + clientId + hostname + orgId 조합이 존재하는지 확인 - */ - Optional findByTimesliceAndApiIdAndClientIdAndHostnameAndOrgId( - LocalDateTime timeslice, - String apiId, - String clientId, - String hostname, - String orgId - ); - - /** - * 특정 시간대의 모든 GwMetric 데이터 조회 - */ - List findByTimeslice(LocalDateTime timeslice); - - /** - * 특정 시간 범위의 GwMetric 데이터 조회 - */ - List findByTimesliceBetween(LocalDateTime startTime, LocalDateTime endTime); -} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java index 52d0dea..e2496fb 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/obp/ObpGwMetricService.java @@ -1,6 +1,7 @@ package com.eactive.eai.rms.data.entity.onl.apim.obp; import com.eactive.apim.portal.obp.entity.ObpGwMetric; +import com.eactive.apim.portal.obp.repository.ObpGwMetricRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; From 64aeedde2edc7f6d90d38d25bf5b8abda521b7bf Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 16 Dec 2025 19:37:04 +0900 Subject: [PATCH 3/3] =?UTF-8?q?OBP=20GwMetric=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/WEB-INF/springapp-servlet.xml | 1 + http/http-client.env.json | 8 ++ http/obp-GwMetric.http | 6 ++ kjb-docs/jbr.md | 75 ++++++++++++++++ .../EmptyJsonResponseAdviceController.java | 2 +- .../eai/rms/common/util/EndpointLogger.java | 58 ++++++++++++ .../onl/apim/obp/ObpGwMetricController.java | 48 ++++++---- src/main/resources/hotswap-agent.properties | 88 +++++++++++++++++-- 8 files changed, 260 insertions(+), 26 deletions(-) create mode 100644 http/http-client.env.json create mode 100644 http/obp-GwMetric.http create mode 100644 kjb-docs/jbr.md create mode 100644 src/main/java/com/eactive/eai/rms/common/util/EndpointLogger.java diff --git a/WebContent/WEB-INF/springapp-servlet.xml b/WebContent/WEB-INF/springapp-servlet.xml index 067b265..bdcc5db 100644 --- a/WebContent/WEB-INF/springapp-servlet.xml +++ b/WebContent/WEB-INF/springapp-servlet.xml @@ -102,6 +102,7 @@ + diff --git a/http/http-client.env.json b/http/http-client.env.json new file mode 100644 index 0000000..0052354 --- /dev/null +++ b/http/http-client.env.json @@ -0,0 +1,8 @@ +{ + "dev": { + "emsUrl": "http://192.168.240.176:30100/monitoring" + }, + "local": { + "emsUrl": "http://127.0.0.1:30100/monitoring" + } +} \ No newline at end of file diff --git a/http/obp-GwMetric.http b/http/obp-GwMetric.http new file mode 100644 index 0000000..2cc592d --- /dev/null +++ b/http/obp-GwMetric.http @@ -0,0 +1,6 @@ +### Retrieve Gateway Metrics After a Specific Time Slice +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 \ No newline at end of file diff --git a/kjb-docs/jbr.md b/kjb-docs/jbr.md new file mode 100644 index 0000000..dbc5d35 --- /dev/null +++ b/kjb-docs/jbr.md @@ -0,0 +1,75 @@ +# 🚀 Spring Legacy (Non-Boot) 핫스왑 환경 구축 가이드 + +본 가이드는 폐쇄망(Air-gapped) 환경에서 **JRebel** 대안으로 \*\*JetBrains Runtime(JBR)\*\*과 **HotswapAgent**를 사용하여, 서버 재시작 없이 Java 코드를 즉시 반영하는 방법을 설명합니다. + +## 1\. 외부망 준비물 (반입 리스트) + +폐쇄망으로 반입해야 할 필수 파일입니다. + +| 항목 | 용도 | 다운로드 링크 (예시) | +| :--- | :--- | :--- | +| **JBR (JetBrains Runtime)** | DCEVM이 내장된 JDK | [JBR GitHub Releases](https://github.com/JetBrains/JetBrainsRuntime/releases) | +| **HotswapAgent.jar** | Spring Bean 설정 갱신용 | [HotswapAgent Releases](https://github.com/HotswapProjects/HotswapAgent/releases) | + +> **주의**: JBR 버전은 프로젝트의 Java 버전(8, 11, 17 등)과 일치시켜 다운로드하세요. + +----- + +## 2\. IDE(IntelliJ / Eclipse) 환경 설정 + +### 2.1 JDK(JBR) 등록 + +1. 반입한 JBR 압축을 풉니다 (예: `C:\dev\jbr`). +2. **IntelliJ**: `Project Structure` \> `SDKs` \> `Add JDK` 후 해당 경로 지정. +3. **Eclipse**: `Preferences` \> `Java` \> `Installed JREs` \> `Add` 후 해당 경로 지정. + +### 2.2 VM 옵션 추가 + +서버(Tomcat 등)의 실행 구성(Run/Debug Configuration)에 아래의 **VM Options**를 추가합니다. + +```bash +# 클래스 구조 변경(필드/메서드 추가) 허용 +-XX:+AllowEnhancedClassRedefinition + +# Spring Bean 설정 및 리소스 실시간 반영 (경로 주의) +-javaagent:C:\path\to\hotswap-agent.jar +``` + +----- + +## 3\. IDE별 세부 최적화 설정 + +### IntelliJ IDEA + +1. **컴파일러 설정**: + - `Settings` \> `Build, Execution, Deployment` \> `Compiler` + - **Build project automatically** 체크 +2. **고급 설정**: + - `Settings` \> `Advanced Settings` + - **Allow auto-make to start even if developed application is currently running** 체크 +3. **업데이트 정책**: + - Tomcat 설정 내 `On 'Update' action` 및 `On frame deactivation`을 **Update classes and resources**로 변경. + +### Eclipse + +1. **Auto Build**: `Project` \> `Build Automatically` 체크 확인. +2. **Debug 모드**: 반드시 **Debug** 모드로 서버를 실행해야 핫스왑이 작동합니다. + +----- + +## 4\. 실제 개발 워크플로우 + +1. **서버 실행**: 반드시 **Debug 모드**로 실행합니다. +2. **코드 수정**: 메서드 로직, 필드 추가, 새로운 `@Service` 생성 등을 수행합니다. +3. **반영 (Compile)**: + - **IntelliJ**: `Ctrl + Shift + F9` (현재 파일 재컴파일) 또는 `Ctrl + F9` (전체 빌드). + - **Eclipse**: 저장(`Ctrl + S`) 시 자동 컴파일 및 반영. +4. **확인**: 서버 재시작 없이 브라우저에서 새로고침하여 결과를 확인합니다. + +----- + +## 5\. 주요 주의사항 (Troubleshooting) + +* **상속 구조 변경**: 클래스의 부모를 바꾸거나 인터페이스를 변경하는 수준의 큰 수정은 서버 재시작이 필요할 수 있습니다. +* **정적 변수(Static)**: `static` 필드의 초기값 변경은 핫스왑 후에도 이전 값이 유지될 수 있습니다. +* **폐쇄망 라이브러리**: `hotswap-agent.jar` 외에 특정 프레임워크(예: MyBatis, Hibernate) 전용 플러그인이 필요한 경우, HotswapAgent 설정 파일(`hotswap-agent.properties`)을 통해 제어할 수 있습니다. \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java b/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java index 4e8ae30..525f5be 100644 --- a/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java +++ b/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java @@ -28,7 +28,7 @@ public class EmptyJsonResponseAdviceController implements ResponseBodyAdvice { + + private static final Logger log = LoggerFactory.getLogger(EndpointLogger.class); + + private boolean logged = false; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // 중복 출력 방지 (parent/child context) + if (logged) { + return; + } + + ApplicationContext ctx = event.getApplicationContext(); + + try { + RequestMappingHandlerMapping mapping = ctx.getBean(RequestMappingHandlerMapping.class); + Map methods = mapping.getHandlerMethods(); + + log.debug("========== Registered HTTP Endpoints ({}) ==========", methods.size()); + + methods.entrySet().stream() + .sorted((e1, e2) -> e1.getKey().toString().compareTo(e2.getKey().toString())) + .forEach(entry -> { + RequestMappingInfo info = entry.getKey(); + HandlerMethod method = entry.getValue(); + log.debug("{} -> {}.{}", + info.getPatternsCondition(), + method.getBeanType().getSimpleName(), + method.getMethod().getName()); + }); + + log.debug("========== End of Endpoints =========="); + logged = true; + + } catch (Exception e) { + log.warn("Failed to log endpoints: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java index df92b9b..164bc3f 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.nio.charset.StandardCharsets; @@ -27,7 +28,7 @@ public class ObpGwMetricController implements InterceptorSkipController { private static final Logger log = LoggerFactory.getLogger(ObpGwMetricController.class); private static final DateTimeFormatter PATH_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss"); + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss"); private final ObpGwMetricManService obpGwMetricManService; @@ -39,15 +40,18 @@ public class ObpGwMetricController implements InterceptorSkipController { * 특정 시간대의 GwMetric 데이터 조회 * * @param datetime 조회 시간대 (형식: yyyy-MM-dd_HH:mm:ss) + * @param pretty true면 JSON 들여쓰기 적용 * @return JSON Array */ @RequestMapping( - value = "/kjb/gw-metrics/{datetime}", + value = "/kjb/gw-metrics/{datetime}.json", produces = "application/json;charset=utf-8" ) @ResponseBody - public String getGwMetrics(@PathVariable("datetime") String datetime) { - log.debug("GwMetric 조회 요청: datetime={}", datetime); + public String getGwMetrics( + @PathVariable("datetime") String datetime, + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("GwMetric 조회 요청: datetime={}, pretty={}", datetime, pretty); long startTime = System.currentTimeMillis(); try { @@ -59,11 +63,7 @@ public class ObpGwMetricController implements InterceptorSkipController { List metrics = obpGwMetricManService.findByTimeslice(timeslice); // JSON 변환 (GSON) - Gson gson = new GsonBuilder() - .serializeNulls() - .create(); - - String json = gson.toJson(metrics); + String json = toJson(metrics, pretty); // 응답 크기 계산 (UTF-8 기준) int jsonBytes = json.getBytes(StandardCharsets.UTF_8).length; @@ -85,15 +85,18 @@ public class ObpGwMetricController implements InterceptorSkipController { * 특정 시간대 이후의 GwMetric 데이터 조회 * * @param datetime 조회 시작 시간대 (형식: yyyy-MM-dd_HH:mm:ss) + * @param pretty true면 JSON 들여쓰기 적용 * @return JSON Array (해당 시간대 이후의 모든 지표) */ @RequestMapping( - value = "/kjb/gw-metrics/after-timeslice/{datetime}", + value = "/kjb/gw-metrics/after-timeslice/{datetime}.json", produces = "application/json;charset=utf-8" ) @ResponseBody - public String getGwMetricsAfterTimeslice(@PathVariable("datetime") String datetime) { - log.debug("GwMetric after-timeslice 조회 요청: datetime={}", datetime); + public String getGwMetricsAfterTimeslice( + @PathVariable("datetime") String datetime, + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("GwMetric after-timeslice 조회 요청: datetime={}, pretty={}", datetime, pretty); long startTime = System.currentTimeMillis(); try { @@ -105,11 +108,7 @@ public class ObpGwMetricController implements InterceptorSkipController { List metrics = obpGwMetricManService.findByTimesliceAfter(timeslice); // JSON 변환 (GSON) - Gson gson = new GsonBuilder() - .serializeNulls() - .create(); - - String json = gson.toJson(metrics); + String json = toJson(metrics, pretty); // 응답 크기 계산 (UTF-8 기준) int jsonBytes = json.getBytes(StandardCharsets.UTF_8).length; @@ -127,6 +126,21 @@ public class ObpGwMetricController implements InterceptorSkipController { } } + /** + * 객체를 JSON 문자열로 변환 + * + * @param object 변환할 객체 + * @param pretty true면 들여쓰기 적용 + * @return JSON 문자열 + */ + private String toJson(Object object, Boolean pretty) { + GsonBuilder builder = new GsonBuilder().serializeNulls(); + if (Boolean.TRUE.equals(pretty)) { + builder.setPrettyPrinting(); + } + return builder.create().toJson(object); + } + /** * 바이트 크기를 읽기 쉬운 형식으로 변환 * 예: 1024 -> "1.0 KB", 1048576 -> "1.0 MB" diff --git a/src/main/resources/hotswap-agent.properties b/src/main/resources/hotswap-agent.properties index 3371a8b..cc2d7ce 100644 --- a/src/main/resources/hotswap-agent.properties +++ b/src/main/resources/hotswap-agent.properties @@ -1,9 +1,81 @@ -# HotSwapAgent 설정 -# Spring 관련 -Spring.reload=true -# Hibernate 관련 -Hibernate.reload=true -# 로그 레벨 -logLevel=INFO -# 자동 Hot Swap 활성화 +# ============================================================ +# HotswapAgent Configuration for eapim-admin (Spring Legacy) +# ============================================================ + +# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654 autoHotswap=true + +# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR) +LOGGER.level=INFO + +# ============================================================ +# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84) +# ============================================================ +disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin + +# ============================================================ +# Core Plugin \uD65C\uC131\uD654 +# ============================================================ + +# Spring Framework (\uD544\uC218) - Bean \uC7AC\uB4F1\uB85D +spring.enabled=true +spring.basePackagePrefix=com.eactive. + +# Hibernate/JPA - \uC5D4\uD2F0\uD2F0 \uBCC0\uACBD \uAC10\uC9C0 +hibernate.enabled=true + +# Logback - \uB85C\uADF8 \uC124\uC815 \uB9AC\uB85C\uB4DC +logback.enabled=true + +# Proxy - AOP \uD504\uB85D\uC2DC \uC7AC\uC0DD\uC131 +proxy.enabled=true + +# ============================================================ +# \uBE44\uD65C\uC131\uD654 Plugin (\uBBF8\uC0AC\uC6A9 \uB610\uB294 \uCDA9\uB3CC \uBC29\uC9C0) +# ============================================================ + +# iBatis (Spring\uC758 SqlMapClientFactoryBean\uACFC \uCDA9\uB3CC) +ibatis.enabled=false +mybatis.enabled=false +jsf.enabled=false +seam.enabled=false +wicket.enabled=false +mojarra.enabled=false +omnifaces.enabled=false +el.enabled=false +weld.enabled=false +owb.enabled=false +cdi.enabled=false +resteasy.enabled=false +jersey1.enabled=false +jersey2.enabled=false + +# ============================================================ +# Watch Resources (\uB9AC\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0) +# ============================================================ + +# \uD074\uB798\uC2A4\uD328\uC2A4 \uC678 \uCD94\uAC00 \uAC10\uC2DC \uACBD\uB85C (XML \uC124\uC815 \uB4F1) +watchResources=src/main/resources,WebContent/WEB-INF + +# ============================================================ +# Spring \uC804\uC6A9 \uC124\uC815 +# ============================================================ + +# \uCEF4\uD3EC\uB10C\uD2B8 \uC2A4\uCE94 \uBCA0\uC774\uC2A4 \uD328\uD0A4\uC9C0 +spring.scanBasePackage=com.eactive.eai,com.eactive.apim + +# \uD504\uB85D\uC2DC \uC7AC\uC0DD\uC131 (AOP, @Transactional \uC0AC\uC6A9 \uC2DC \uD544\uC218) +spring.proxyRegeneration=true + +# Bean \uC815\uC758 \uBCC0\uACBD \uC2DC \uB9AC\uB85C\uB4DC +spring.reloadBeanDefinition=true + +# ============================================================ +# \uB514\uBC84\uAE45 (\uBB38\uC81C \uBC1C\uC0DD \uC2DC \uD65C\uC131\uD654) +# ============================================================ + +# \uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825 (\uD544\uC694\uC2DC DEBUG\uB85C \uBCC0\uACBD) +# LOGGER.level=DEBUG + +# \uD074\uB798\uC2A4 \uBCC0\uACBD \uCD94\uC801 +# LOGGER.org.hotswap.agent.plugin.spring=DEBUG