From 0d7d006f92e6a81a52247ce8bb0ca7d7b16c99db Mon Sep 17 00:00:00 2001 From: eastargh Date: Mon, 1 Jun 2026 17:37:06 +0900 Subject: [PATCH 01/12] =?UTF-8?q?api=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=ED=99=94=20=EA=B0=90=EC=A7=80=20&=20=EC=9B=B9=ED=9B=85=20?= =?UTF-8?q?=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/WEB-INF/applicationContext-jpa.xml | 1 + .../data/entity/man/user/UserInfoService.java | 11 ++ .../onl}/djb/apistatus/ApiStatus.java | 2 +- .../onl}/djb/apistatus/ApiStatusEvent.java | 3 +- .../djb/inflow/InflowTokenInsufficient.java | 2 +- .../inflow/InflowTokenInsufficientLog.java | 2 +- .../inflow/InflowTokenInsufficientLogId.java | 2 +- .../entity/onl/djb/webhook/WebhookReq.java | 33 ++++ .../entity/onl/djb/webhook/WebhookReqApi.java | 19 +++ .../onl/djb/webhook/WebhookReqApiId.java | 25 ++++ .../onl/djb/webhook/WebhookReqEvent.java | 19 +++ .../onl/djb/webhook/WebhookReqEventId.java | 25 ++++ .../onl}/djb/webhook/WebhookSendLog.java | 2 +- .../ext/djb/apistatus/ApiStatusService.java | 69 --------- .../djb/apistatus/ApiStatusRepository.java | 20 ++- .../ext/djb/apistatus/ApiStatusService.java | 141 ++++++++++++++++++ .../eai/rms/ext/djb/async/AsyncConfig.java | 25 ---- .../rms/ext/djb/async/AsyncEventListener.java | 40 ----- .../ext/djb/event/ApiStatusChangedEvent.java | 18 --- .../InflowTokenInsufficientEvent.java | 4 +- .../ext/djb/inflow/InflowTokenRepository.java | 5 +- .../ext/djb/inflow/InflowTokenService.java | 13 +- .../rms/ext/djb/job/ApiStatusMonitorJob.java | 4 +- .../ext/djb/job/InflowTokenMonitorJob.java | 2 +- .../djb/swing/service/SwingSendManager.java | 17 --- .../eai/rms/ext/djb/util/UmsManager.java | 71 +++++++++ .../controller/WebhookSendController.java | 14 +- .../djb/webhook/dto/WebhookSendRequest.java | 3 +- .../repository/WebhookReqRepository.java | 11 ++ .../repository/WebhookSendLogRepository.java | 2 +- .../webhook/service/WebhookSendManager.java | 17 --- .../djb/webhook/service/WebhookService.java | 74 +++++++-- .../service/ums/UmsDispatchService.java | 86 +++++------ 33 files changed, 505 insertions(+), 277 deletions(-) rename src/main/java/com/eactive/eai/rms/data/{ext => entity/onl}/djb/apistatus/ApiStatus.java (95%) rename src/main/java/com/eactive/eai/rms/data/{ext => entity/onl}/djb/apistatus/ApiStatusEvent.java (54%) rename src/main/java/com/eactive/eai/rms/data/{ext => entity/onl}/djb/inflow/InflowTokenInsufficient.java (66%) rename src/main/java/com/eactive/eai/rms/data/{ext => entity/onl}/djb/inflow/InflowTokenInsufficientLog.java (95%) rename src/main/java/com/eactive/eai/rms/data/{ext => entity/onl}/djb/inflow/InflowTokenInsufficientLogId.java (90%) create mode 100644 src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java create mode 100644 src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java create mode 100644 src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java create mode 100644 src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java create mode 100644 src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java rename src/main/java/com/eactive/eai/rms/data/{ext => entity/onl}/djb/webhook/WebhookSendLog.java (97%) delete mode 100644 src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java rename src/main/java/com/eactive/eai/rms/{data => }/ext/djb/apistatus/ApiStatusRepository.java (80%) create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java delete mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java delete mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java delete mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java rename src/main/java/com/eactive/eai/rms/ext/djb/{event => inflow}/InflowTokenInsufficientEvent.java (80%) rename src/main/java/com/eactive/eai/rms/{data => }/ext/djb/inflow/InflowTokenRepository.java (73%) rename src/main/java/com/eactive/eai/rms/{data => }/ext/djb/inflow/InflowTokenService.java (67%) delete mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java delete mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java diff --git a/WebContent/WEB-INF/applicationContext-jpa.xml b/WebContent/WEB-INF/applicationContext-jpa.xml index b1da502..95e9929 100644 --- a/WebContent/WEB-INF/applicationContext-jpa.xml +++ b/WebContent/WEB-INF/applicationContext-jpa.xml @@ -69,6 +69,7 @@ + findAllByRoleId(String roleId) { + QUserInfo qUserInfo = QUserInfo.userInfo; + QUserRole qUserRole = QUserRole.userRole; + + return getJPAQueryFactory() + .selectFrom(qUserInfo) + .join(qUserRole).on(qUserInfo.userid.eq(qUserRole.id.userId)) + .where(qUserRole.id.roleId.eq(roleId)) + .fetch(); + } + public Page findByUsername(Pageable pageable, String username) { QUserInfo qUserInfo = QUserInfo.userInfo; BooleanExpression predicate = qUserInfo.isNotNull(); diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatus.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatus.java similarity index 95% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatus.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatus.java index c7f1e17..8c46dd5 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatus.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatus.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; +package com.eactive.eai.rms.data.entity.onl.djb.apistatus; import java.time.LocalDateTime; diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusEvent.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatusEvent.java similarity index 54% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusEvent.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatusEvent.java index 5f2049e..aba632a 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusEvent.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/apistatus/ApiStatusEvent.java @@ -1,7 +1,8 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; +package com.eactive.eai.rms.data.entity.onl.djb.apistatus; public interface ApiStatusEvent { String getEaisvcname(); String getEaisvcdesc(); + String getPrevStatusCode(); String getEvent(); } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficient.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficient.java similarity index 66% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficient.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficient.java index c3c6ffc..64108f8 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficient.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficient.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.data.entity.onl.djb.inflow; public interface InflowTokenInsufficient { String getEaisvcname(); diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLog.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLog.java similarity index 95% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLog.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLog.java index ae0ba77..e3cd9eb 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLog.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLog.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.data.entity.onl.djb.inflow; import java.io.Serializable; diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLogId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLogId.java similarity index 90% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLogId.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLogId.java index 0a2bae6..1568a34 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenInsufficientLogId.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/inflow/InflowTokenInsufficientLogId.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.data.entity.onl.djb.inflow; import java.io.Serializable; import javax.persistence.Column; diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java new file mode 100644 index 0000000..02aa430 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReq.java @@ -0,0 +1,33 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "PTL_WEBHOOK_REQ") +@Getter +@NoArgsConstructor +public class WebhookReq { + + @Id + @Column(name = "ID", length = 36, nullable = false) + private String id; + + @Column(name = "ORG_ID", length = 100) + @Comment("법인 ID") + private String orgId; + + @Column(name = "TARGET_URL", length = 500, nullable = false) + @Comment("웹훅 수신 URL") + private String targetUrl; + + @Column(name = "SECRET", length = 200) + @Comment("HMAC 서명용 Secret Key") + private String secret; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java new file mode 100644 index 0000000..df94810 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApi.java @@ -0,0 +1,19 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.io.Serializable; + +@Entity +@Table(name = "PTL_WEBHOOK_REQ_API") +@Getter +@NoArgsConstructor +public class WebhookReqApi implements Serializable { + + @EmbeddedId + private WebhookReqApiId id; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java new file mode 100644 index 0000000..784ea4a --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqApiId.java @@ -0,0 +1,25 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Embeddable +public class WebhookReqApiId implements Serializable { + + @Column(name = "WEBHOOK_REQ_ID", nullable = false, length = 36) + @Comment("웹훅 요청 ID") + private String webhookReqId; + + @Column(name = "API_ID", nullable = false, length = 100) + @Comment("API ID") + private String apiId; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java new file mode 100644 index 0000000..5cc9b4b --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEvent.java @@ -0,0 +1,19 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.io.Serializable; + +@Entity +@Table(name = "PTL_WEBHOOK_REQ_EVENT") +@Getter +@NoArgsConstructor +public class WebhookReqEvent implements Serializable { + + @EmbeddedId + private WebhookReqEventId id; +} diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java new file mode 100644 index 0000000..1af090e --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookReqEventId.java @@ -0,0 +1,25 @@ +package com.eactive.eai.rms.data.entity.onl.djb.webhook; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Embeddable +public class WebhookReqEventId implements Serializable { + + @Column(name = "WEBHOOK_REQ_ID", nullable = false, length = 36) + @Comment("웹훅 요청 ID") + private String webhookReqId; + + @Column(name = "EVENT_TYPE", nullable = false, length = 50) + @Comment("이벤트 유형 (CONTROL_START, ERROR_START 등)") + private String eventType; +} diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/webhook/WebhookSendLog.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java similarity index 97% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/webhook/WebhookSendLog.java rename to src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java index f2bcdf2..a1fa55d 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/webhook/WebhookSendLog.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.webhook; +package com.eactive.eai.rms.data.entity.onl.djb.webhook; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java deleted file mode 100644 index ac35442..0000000 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusService.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; - -import java.util.HashMap; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.eactive.eai.rms.ext.djb.event.ApiStatusChangedEvent; - -@Service -@Transactional -public class ApiStatusService { - - private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class); - - @Autowired - private ApiStatusRepository apiStatusRepository; - - @Autowired - private ApplicationEventPublisher eventPublisher; - - - - public void updateApiStatus(HashMap param) { - - List list = apiStatusRepository.findApiStatusEvents( - Integer.parseInt(param.get("errorRate")), - Integer.parseInt(param.get("errorRangeMinute")), - Integer.parseInt(param.get("delayRangeMinute")), - Integer.parseInt(param.get("delayAvgRespTime")) - ); - - for (ApiStatusEvent event : list) { - String newStatusCode = resolveStatusCode(event.getEvent()); - if (newStatusCode == null) continue; - - ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus()); - - apiStatus.setEaisvcname(event.getEaisvcname()); - apiStatus.setStatusCode(newStatusCode); - - apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT - log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode); - - eventPublisher.publishEvent(ApiStatusChangedEvent.from(event)); // 트랜잭션 커밋 후 리스너 실행 - log.debug("이벤트 전파"); - } - } - - - private String resolveStatusCode(String event) { - switch (event) { - case "CONTROL_START": return "C"; //점검 - case "CONTROL_END": return "N"; //정상 - case "ERROR_START": return "E"; //장애 - case "ERROR_END": return "N"; //정상 - case "DELAY_START": return "D"; //지연 - case "DELAY_END": return "N"; //정상 - default: - log.warn("알 수 없는 이벤트: {}", event); - return null; - } - } -} diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java similarity index 80% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusRepository.java rename to src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java index f716bb0..ec80426 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/apistatus/ApiStatusRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java @@ -1,10 +1,12 @@ -package com.eactive.eai.rms.data.ext.djb.apistatus; +package com.eactive.eai.rms.ext.djb.apistatus; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent; public interface ApiStatusRepository extends BaseRepository { @@ -22,9 +24,11 @@ public interface ApiStatusRepository extends BaseRepository { @Query(nativeQuery = true, value = " SELECT EAISVCNAME" + " , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC" + + " , PREV_STATUS_CODE " + " , EVENT" + " FROM (" + " SELECT EAISVCNAME" + + " , STATUS_CODE AS PREV_STATUS_CODE " + " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'" + " WHEN STATUS_CODE = 'C' AND CTRL_YN = 'N' THEN 'CONTROL_END'" + " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'" @@ -34,14 +38,14 @@ public interface ApiStatusRepository extends BaseRepository { + " ELSE 'STAY' END AS EVENT" + " FROM (" + " SELECT A.EAISVCNAME" - + " , NVL(B.STATUS_CODE, '1') AS STATUS_CODE" - + " , NVL((SELECT 'Y' FROM TSEAITI01" - + " WHERE (TO_CHAR(SYSDATE,'HH24MI') BETWEEN SUBSTR(EAICTRLDSTICCTNT,16,4) AND SUBSTR(EAICTRLDSTICCTNT,21,4)" - + " OR SUBSTR(EAICTRLDSTICCTNT,16,9) = '0000|0000')" - + " AND A.EAISVCNAME = EAICTRLNAME),'N') AS CTRL_YN" + + " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE" + + " , NVL(( SELECT 'Y' FROM EMSAPP.PTL_NOTICE_API X " + + " WHERE EXISTS (SELECT 1 FROM EMSAPP.PTL_NOTICE Y WHERE X.NOTICE_ID = Y.ID " + + " AND TO_CHAR(SYSDATE, 'YYYYMMDDHH24MI') BETWEEN START_TIME AND END_TIME) " + + " AND A.EAISVCNAME = X.API_NAME),'N') AS CTRL_YN " + " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'" - + " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'" - + " ELSE 'N' END" + + " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'" + + " ELSE 'N' END" + " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL" + " , SUM(SUCCESS_CNT) AS SUCCESS" + " FROM API_STATS_MINUTE" diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java new file mode 100644 index 0000000..08e6bce --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java @@ -0,0 +1,141 @@ +package com.eactive.eai.rms.ext.djb.apistatus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus; +import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent; +import com.eactive.eai.rms.ext.djb.util.UmsManager; +import com.eactive.ext.djb.apistatus.DjbIncidentDetectionService; + + +@Service +@Transactional +public class ApiStatusService { + + private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class); + + @Autowired + private ApiStatusRepository apiStatusRepository; + + @Autowired + private UmsManager ums; + + @Autowired + private DjbIncidentDetectionService djbService; + + + public void updateApiStatus(HashMap param) { + + List list = apiStatusRepository.findApiStatusEvents( + Integer.parseInt(param.get("errorRate")), + Integer.parseInt(param.get("errorRangeMinute")), + Integer.parseInt(param.get("delayRangeMinute")), + Integer.parseInt(param.get("delayAvgRespTime")) + ); + + HashMap> eventMap = new HashMap<>(); + + + for (ApiStatusEvent event : list) { + String newStatusCode = resolveStatusCode(event.getEvent()); + if (newStatusCode == null) continue; + + ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus()); + + apiStatus.setEaisvcname(event.getEaisvcname()); + apiStatus.setStatusCode(newStatusCode); + + apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT + log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode); + + + //이벤트별로 api분리 저장 + List apis = (List)eventMap.get(event.getEvent()); + if (apis == null) { + apis = new ArrayList(); + apis.add(event.getEaisvcname()); + eventMap.put(event.getEvent(), apis); + } else { + apis.add(event.getEaisvcname()); + } + } + + + //이벤트별로 ums, webhook을 발송한다 + for (Map.Entry> entry : eventMap.entrySet()) { + String event = entry.getKey(); + List apiIds = entry.getValue(); + + String message = getSwingChatMessage(event, apiIds); + ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message); + + //제휴사 webhook 발송 + ums.sendWebhook(event, apiIds); + + //개발자포탈 API상태모니터링 화면을 위한 처리 + djbService.detect(event, apiIds); + + +// if (event.endsWith("_END")) { +// djbService.recovered(event, apiIds); +// } else { +// djbService.detect(event, apiIds); +// } + } + } + + + private String resolveStatusCode(String event) { + switch (event) { + case "CONTROL_START": return "C"; //점검 + case "CONTROL_END": return "N"; //정상 + case "ERROR_START": return "E"; //장애 + case "ERROR_END": return "N"; //정상 + case "DELAY_START": return "D"; //지연 + case "DELAY_END": return "N"; //정상 + default: + log.warn("알 수 없는 이벤트: {}", event); + return null; + } + } + + + private String getSwingChatMessage(String event, List apiIds) { + String message = ""; + switch (event) { + case "CONTROL_START": + message = "API 서비스 점검이 시작되었습니다"; //점검 + break; + case "CONTROL_END": + message = "API 서비스 점검이 완료되었습니다"; //정상 + break; + case "ERROR_START": + message = "API 서비스 장애가 발생하였습니다"; //장애 + break; + case "ERROR_END": + message = "API 서비스 장애가 복구되었습니다"; //정상 + break; + case "DELAY_START": + message = "API 서비스 지연이 발생하였습니다"; //지연 + break; + case "DELAY_END": + message = "API 서비스 지연이 복구되었습니다"; //정상 + break; + default: + message = ""; + } + + return message + "\n- " + String.join(",", apiIds); + } +} + diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java b/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java deleted file mode 100644 index b62f619..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.eactive.eai.rms.ext.djb.async; - -import java.util.concurrent.Executor; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -@Configuration -@EnableAsync -public class AsyncConfig { - - @Bean("asyncExecutor") - public Executor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(2); - executor.setMaxPoolSize(5); - executor.setQueueCapacity(20); - executor.setThreadNamePrefix("async-"); - executor.initialize(); - return executor; - } - -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java b/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java deleted file mode 100644 index 63c5089..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/async/AsyncEventListener.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.eactive.eai.rms.ext.djb.async; - -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalEventListener; - -import com.eactive.eai.rms.ext.djb.event.ApiStatusChangedEvent; -import com.eactive.eai.rms.ext.djb.event.InflowTokenInsufficientEvent; -import com.eactive.eai.rms.ext.djb.swing.service.SwingSendManager; -import com.eactive.eai.rms.ext.djb.webhook.service.WebhookSendManager; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Component -@RequiredArgsConstructor -public class AsyncEventListener { - - private final WebhookSendManager webhookSendManager; - - private final SwingSendManager swingSendManager; - - @Async("asyncExecutor") - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - public void onApiStatusChanged(ApiStatusChangedEvent event) { - log.debug("API 상태 변경 이벤트 수신: {} {}", event.getEaisvcname(), event.getEvent()); - webhookSendManager.send(event.getEaisvcname(), event.getEvent()); - swingSendManager.send(null, 0, 0); - } - - @Async("asyncExecutor") - @EventListener // 트랜잭션 write 없이 read만 하므로 TransactionalEventListener 불필요 - public void onInflowTokenFail(InflowTokenInsufficientEvent event) { - log.debug("유량제어 토큰획득 실패 이벤트 수신: {} {}건", event.getEaisvcname(), event.getCnt()); - swingSendManager.send(event.getEaisvcname(), event.getCnt(), event.getRangeMinute()); - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java b/src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java deleted file mode 100644 index 6f6dc95..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/event/ApiStatusChangedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.eactive.eai.rms.ext.djb.event; - -import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusEvent; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class ApiStatusChangedEvent { - private final String eaisvcname; - private final String eaisvcdesc; - private final String event; - - public static ApiStatusChangedEvent from(ApiStatusEvent source) { - return new ApiStatusChangedEvent(source.getEaisvcname(), source.getEaisvcdesc(), source.getEvent()); - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/event/InflowTokenInsufficientEvent.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java similarity index 80% rename from src/main/java/com/eactive/eai/rms/ext/djb/event/InflowTokenInsufficientEvent.java rename to src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java index b4b010e..dadcd80 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/event/InflowTokenInsufficientEvent.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java @@ -1,6 +1,6 @@ -package com.eactive.eai.rms.ext.djb.event; +package com.eactive.eai.rms.ext.djb.inflow; -import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenInsufficient; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenRepository.java similarity index 73% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenRepository.java rename to src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenRepository.java index 8dadc25..d98c99c 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenRepository.java @@ -1,9 +1,12 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.ext.djb.inflow; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficientLog; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficientLogId; public interface InflowTokenRepository extends BaseRepository { diff --git a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenService.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java similarity index 67% rename from src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenService.java rename to src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java index e4e91fa..fa7a12c 100644 --- a/src/main/java/com/eactive/eai/rms/data/ext/djb/inflow/InflowTokenService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java @@ -1,4 +1,4 @@ -package com.eactive.eai.rms.data.ext.djb.inflow; +package com.eactive.eai.rms.ext.djb.inflow; import java.util.List; @@ -9,7 +9,9 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.eactive.eai.rms.ext.djb.event.InflowTokenInsufficientEvent; +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; +import com.eactive.eai.rms.ext.djb.util.UmsManager; @Service @@ -22,16 +24,15 @@ public class InflowTokenService { private InflowTokenRepository inflowTokenRepository; @Autowired - private ApplicationEventPublisher eventPublisher; + private UmsManager ums; public void checkRecentFails(long rangeMinute) { List rows = inflowTokenRepository.countTokenInsufficient(rangeMinute); for (InflowTokenInsufficient info : rows) { log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt()); - - //내부직원 알림 발송 - eventPublisher.publishEvent(InflowTokenInsufficientEvent.from(info, rangeMinute)); + String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname(); + ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message); } } } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java index dbd3b66..6d0dd5c 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/ApiStatusMonitorJob.java @@ -17,7 +17,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; import com.eactive.eai.rms.common.util.CommonUtil; -import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusService; +import com.eactive.eai.rms.ext.djb.apistatus.ApiStatusService; import com.eactive.eai.rms.onl.common.util.DateUtil; /** @@ -88,11 +88,11 @@ public class ApiStatusMonitorJob implements Job { monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext"); ApiStatusService apiStatusUpdateService = appContext.getBean(ApiStatusService.class); - DataSourceContextHolder.setDataSourceType( DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); try { apiStatusUpdateService.updateApiStatus(param); + } catch (Exception e) { log.error("ApiStatusUpdateJob execution failed", e); throw new JobExecutionException(e); diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java index f9dc9b7..bcb1a5f 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/InflowTokenMonitorJob.java @@ -18,7 +18,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.datasource.DataSourceContextHolder; import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; import com.eactive.eai.rms.common.util.CommonUtil; -import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenService; +import com.eactive.eai.rms.ext.djb.inflow.InflowTokenService; import com.eactive.eai.rms.onl.common.util.DateUtil; /** diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java deleted file mode 100644 index 34f61a2..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/swing/service/SwingSendManager.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.eactive.eai.rms.ext.djb.swing.service; - -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -public class SwingSendManager { - - public void send(String eaisvcname, long cnt, long rangeMinute) { - log.info("[Swing] 발송 준비: {} 최근 {}분 {}건", eaisvcname, rangeMinute, cnt); - // TODO: 알림 발송 로직 - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java new file mode 100644 index 0000000..ac1479d --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java @@ -0,0 +1,71 @@ +package com.eactive.eai.rms.ext.djb.util; + +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.template.service.MessageHandlerService; +import com.eactive.apim.portal.template.service.MessageRecipient; +import com.eactive.apim.portal.user.entity.UserInfo; +import com.eactive.eai.rms.data.entity.man.user.UserInfoService; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; +import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class UmsManager { + + private static final Logger log = LoggerFactory.getLogger(UmsManager.class); + + private final MessageHandlerService messageHandlerService; + private final UserInfoService userInfoService; + private final WebhookService webhookService; + + /** + * 내부직원 메신저(Swing chat) 발송 + * @param roleId + * @param messageCode + * @param message + * @return + */ + public void sendMessenger(String roleId, MessageCode messageCode, String message) { + + List users = userInfoService.findAllByRoleId(roleId); + if (users.isEmpty()) { + log.warn("sendMessenger: no users found for role '{}'", roleId); + return; + } + + for (UserInfo user : users) { + MessageRecipient recipient = MessageRecipient.builder() + .userId(user.getUserid()) + .username(user.getUsername()) + .build(); + + HashMap params = new HashMap<>(); + params.put("message", message); + + messageHandlerService.publishEvent(messageCode, recipient, params); + } + } + + /** + * event를 구둑중인 제휴사에게 웹훅 발송 + * @param event + * @param apiIds + */ + public void sendWebhook(String event, List apiIds) { + + List list = webhookService.findSendList(event, apiIds); + for (WebhookSendRequest req : list) { + webhookService.send(req); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java index 3f2a1a0..f338fbe 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; import com.eactive.eai.rms.ext.djb.webhook.service.WebhookReceiveService; import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService; @@ -38,16 +38,12 @@ public class WebhookSendController { public ResponseEntity> sendWebhook( @RequestBody WebhookSendRequest request) { - WebhookSendLog result = webhookService.send( - request.getTargetUrl(), - request.getEventType(), - request.getData() - ); + webhookService.send(request); Map response = new HashMap<>(); - response.put("logId", result.getId()); - response.put("success", result.getSuccess()); - response.put("statusCode", result.getStatusCode()); +// response.put("logId", result.getId()); +// response.put("success", result.getSuccess()); +// response.put("statusCode", result.getStatusCode()); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java index 6250bc0..264338e 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java @@ -6,8 +6,9 @@ import lombok.Setter; @Getter @Setter public class WebhookSendRequest { - + private String orgId; private String targetUrl; private String eventType; + private String secret; private Object data; } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java new file mode 100644 index 0000000..7abc722 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookReqRepository.java @@ -0,0 +1,11 @@ +package com.eactive.eai.rms.ext.djb.webhook.repository; + +import com.eactive.eai.rms.data.EMSDataSource; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookReq; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +@EMSDataSource +public interface WebhookReqRepository extends JpaRepository { +} diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java index 08141d6..2233a9c 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java @@ -10,7 +10,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.eactive.eai.rms.data.EMSDataSource; -import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; @Repository @EMSDataSource diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java deleted file mode 100644 index d2f5f59..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendManager.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.eactive.eai.rms.ext.djb.webhook.service; - -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j - @Service - @RequiredArgsConstructor - public class WebhookSendManager { - - public void send(String eaisvcname, String eventType) { - log.info("[Webhook] 발송 준비: {} {}", eaisvcname, eventType); - // TODO: PTL_WEBHOOK_REQ 조인 조회 후 WebhookService.send() 호출 - } - } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java index a7f1f5d..afde0bf 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java @@ -2,12 +2,16 @@ package com.eactive.eai.rms.ext.djb.webhook.service; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -18,10 +22,16 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; -import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReq; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqApi; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqEvent; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookPayload; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository; import com.fasterxml.jackson.databind.ObjectMapper; +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,17 +45,58 @@ public class WebhookService { private final ObjectMapper objectMapper; private final RestTemplate restTemplate; - - private String secretKey = "HW8JtFpkPmQqsVmr0Rb81P4qDypaNIVbGf3ZNMMPfyerhOHshoKABLaMBo1HA4BldTxhw1NjYmVnoPu9IIV7cyRXjecL3b2UctyR7DnVaJausltZLLr8Qm4Bzs4wRmgf"; + @PersistenceContext(unitName = "entityManagerFactoryForEMS") + private EntityManager entityManager; private static final String HMAC_ALGORITHM = "HmacSHA256"; private static final int MAX_RETRY = 3; + + + @SuppressWarnings("unchecked") + public List findSendList(String event, List apiIds) { + QWebhookReq req = QWebhookReq.webhookReq; + QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent; + QWebhookReqApi api = QWebhookReqApi.webhookReqApi; + + List tuples = new JPAQueryFactory(entityManager) + .select(req.orgId, req.targetUrl, req.secret, api.id.apiId) + .from(req) + .join(evt).on(req.id.eq(evt.id.webhookReqId)) + .join(api).on(req.id.eq(api.id.webhookReqId)) + .where(evt.id.eventType.eq(event) + .and(api.id.apiId.in(apiIds))) + .fetch(); + + // orgId + targetUrl + secret 기준으로 그룹핑, apiId를 data(List)로 집계 + Map resultMap = new LinkedHashMap<>(); + for (Tuple t : tuples) { + String key = t.get(req.orgId) + "|" + t.get(req.targetUrl); + resultMap.computeIfAbsent(key, k -> { + WebhookSendRequest wr = new WebhookSendRequest(); + wr.setOrgId(t.get(req.orgId)); + wr.setTargetUrl(t.get(req.targetUrl)); + wr.setSecret(t.get(req.secret)); + wr.setEventType(event); + wr.setData(new ArrayList()); + return wr; + }); + ((List) resultMap.get(key).getData()).add(t.get(api.id.apiId)); + } + + return new ArrayList<>(resultMap.values()); + } /** * 웹훅 발송 메인 메서드 */ @Transactional - public WebhookSendLog send(String targetUrl, String eventType, Object data) { + public void send(WebhookSendRequest req) { + + String targetUrl = req.getTargetUrl(); + String secret = req.getSecret(); + String eventType = req.getEventType(); + Object data = req.getData(); + WebhookSendLog sendLog = new WebhookSendLog(); sendLog.setTargetUrl(targetUrl); sendLog.setEventType(eventType); @@ -57,7 +108,7 @@ public class WebhookService { sendLog.setPayload(payloadJson); // 2. Secret Key로 HMAC-SHA256 서명 생성 - String signature = generateSignature(payloadJson); + String signature = generateSignature(secret, payloadJson); sendLog.setSignature(signature); // 3. HTTP 요청 헤더 구성 @@ -95,12 +146,13 @@ public class WebhookService { eventType, targetUrl, e.getMessage()); } - return sendLogRepository.save(sendLog); + sendLogRepository.save(sendLog); + } /** * 실패 건 재시도 - */ + @Transactional public void retryFailedWebhooks() { List failedLogs = @@ -119,16 +171,16 @@ public class WebhookService { log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e); } } - } + }*/ /** * HMAC-SHA256 서명 생성 * Java 17 미만은 HexFormat 미지원이므로 직접 변환 */ - private String generateSignature(String payload) throws Exception { + private String generateSignature(String secret, String payload) throws Exception { Mac mac = Mac.getInstance(HMAC_ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec( - secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM); + secret.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM); mac.init(keySpec); byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java index dc952fa..8d3e521 100644 --- a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java @@ -68,14 +68,14 @@ public class UmsDispatchService { log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton"); } -// private KjbUmsService getUmsService() { -// return KjbUmsService.getInstance(); -// } -// -// private KjbEmailSendModule getEmailModule() { -// return KjbEmailSendModule.getInstance(); -// } -// + private KjbUmsService getUmsService() { + return KjbUmsService.getInstance(); + } + + private KjbEmailSendModule getEmailModule() { + return KjbEmailSendModule.getInstance(); + } + @SuppressWarnings("unchecked") @Transactional(transactionManager = "transactionManagerForEMS") public void processMessages() { @@ -112,7 +112,7 @@ public class UmsDispatchService { log.info("Starting to submit {} messages for processing", messagesToProcess.size()); for (MessageRequest message : messagesToProcess) { - //submitMessageTask(message); + submitMessageTask(message); } } else { log.info("No pending messages found"); @@ -123,40 +123,40 @@ public class UmsDispatchService { } -// private void submitMessageTask(MessageRequest message) { -// int queueSize = executorService.getQueue().size(); -// log.debug("Current queue size before submission: {}", queueSize); -// -// executorService.submit(() -> { -// try { -// transactionTemplate.execute(status -> { -// try { -// // 광주은행에서는 사용하지 않는 메소드 -//// processMessage(message); -// -// if (KjbUmsService.isAllowChannel(message)) { -// getUmsService().send(message); -// } -// -// if (KjbEmailSendModule.isAllowChannel(message)) { -// getEmailModule().sendEmail(message); -// } -// -// return null; -// } catch (Exception e) { -// log.error("Failed to process message: {} - Error: {}", -// message.getId(), e.getMessage(), e); -// updateMessageStatus(message, "FAILED"); -// status.setRollbackOnly(); -// return null; -// } -// }); -// } catch (Exception e) { -// log.error("Transaction execution failed for message: {} - Error: {}", -// message.getId(), e.getMessage(), e); -// } -// }); -// } + private void submitMessageTask(MessageRequest message) { + int queueSize = executorService.getQueue().size(); + log.debug("Current queue size before submission: {}", queueSize); + + executorService.submit(() -> { + try { + transactionTemplate.execute(status -> { + try { + // 광주은행에서는 사용하지 않는 메소드 +// processMessage(message); + + if (KjbUmsService.isAllowChannel(message)) { + getUmsService().send(message); + } + + if (KjbEmailSendModule.isAllowChannel(message)) { + getEmailModule().sendEmail(message); + } + + return null; + } catch (Exception e) { + log.error("Failed to process message: {} - Error: {}", + message.getId(), e.getMessage(), e); + updateMessageStatus(message, "FAILED"); + status.setRollbackOnly(); + return null; + } + }); + } catch (Exception e) { + log.error("Transaction execution failed for message: {} - Error: {}", + message.getId(), e.getMessage(), e); + } + }); + } /** * kbank 방식으로 광주은행에서는 사용하지 않음 From 35e5564252b0c5b0baaf801b2b10cdb89008bcaa Mon Sep 17 00:00:00 2001 From: eastargh Date: Tue, 2 Jun 2026 09:43:03 +0900 Subject: [PATCH 02/12] =?UTF-8?q?webhook=20=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onl/djb/webhook/WebhookSendLog.java | 3 ++ .../ext/djb/apistatus/ApiStatusService.java | 13 +++----- .../eai/rms/ext/djb/util/UmsManager.java | 33 ++++++++++++++----- .../djb/webhook/service/WebhookService.java | 1 + 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java index a1fa55d..5c82e6e 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java @@ -42,6 +42,9 @@ public class WebhookSendLog { @Column(name = "STATUS_CODE") private Integer statusCode; + @Column(name = "ORG_ID") + private String orgId; + @Lob @Column(name = "RESPONSE_BODY") private String responseBody; diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java index 08e6bce..5bb77d0 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.eai.rms.data.entity.man.role.Role; import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus; import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent; import com.eactive.eai.rms.ext.djb.util.UmsManager; @@ -70,27 +71,21 @@ public class ApiStatusService { } } - //이벤트별로 ums, webhook을 발송한다 for (Map.Entry> entry : eventMap.entrySet()) { String event = entry.getKey(); List apiIds = entry.getValue(); + Role role = new Role(); + role.setRoleId("api-monitor"); String message = getSwingChatMessage(event, apiIds); - ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message); + ums.sendMessenger(role, MessageCode.API_STATUS_CHANGED, message); //제휴사 webhook 발송 ums.sendWebhook(event, apiIds); //개발자포탈 API상태모니터링 화면을 위한 처리 djbService.detect(event, apiIds); - - -// if (event.endsWith("_END")) { -// djbService.recovered(event, apiIds); -// } else { -// djbService.detect(event, apiIds); -// } } } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java index ac1479d..0bb9d21 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java @@ -12,6 +12,7 @@ import com.eactive.apim.portal.template.entity.MessageCode; import com.eactive.apim.portal.template.service.MessageHandlerService; import com.eactive.apim.portal.template.service.MessageRecipient; import com.eactive.apim.portal.user.entity.UserInfo; +import com.eactive.eai.rms.data.entity.man.role.Role; import com.eactive.eai.rms.data.entity.man.user.UserInfoService; import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest; import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService; @@ -29,17 +30,16 @@ public class UmsManager { private final WebhookService webhookService; /** - * 내부직원 메신저(Swing chat) 발송 - * @param roleId + * role 역할을 가진 내부직원에게 메신저(Swing chat) 발송 + * @param role * @param messageCode * @param message * @return */ - public void sendMessenger(String roleId, MessageCode messageCode, String message) { - - List users = userInfoService.findAllByRoleId(roleId); + public void sendMessenger(Role role, MessageCode messageCode, String message) { + List users = userInfoService.findAllByRoleId(role.getRoleId()); if (users.isEmpty()) { - log.warn("sendMessenger: no users found for role '{}'", roleId); + log.warn("sendMessenger: no users found for role '{}'", role.getRoleId()); return; } @@ -55,14 +55,31 @@ public class UmsManager { messageHandlerService.publishEvent(messageCode, recipient, params); } } + + /** + * 내부직원에게 메신저 발송 + * @param user + * @param messageCode + * @param message + */ + public void sendMessenger(UserInfo user, MessageCode messageCode, String message) { + MessageRecipient recipient = MessageRecipient.builder() + .userId(user.getUserid()) + .username(user.getUsername()) + .build(); + + HashMap params = new HashMap<>(); + params.put("message", message); + + messageHandlerService.publishEvent(messageCode, recipient, params); + } /** - * event를 구둑중인 제휴사에게 웹훅 발송 + * event를 구독중인 제휴사에게 웹훅 발송 * @param event * @param apiIds */ public void sendWebhook(String event, List apiIds) { - List list = webhookService.findSendList(event, apiIds); for (WebhookSendRequest req : list) { webhookService.send(req); diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java index afde0bf..60f5c36 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java @@ -98,6 +98,7 @@ public class WebhookService { Object data = req.getData(); WebhookSendLog sendLog = new WebhookSendLog(); + sendLog.setOrgId(req.getOrgId()); sendLog.setTargetUrl(targetUrl); sendLog.setEventType(eventType); From 68c8e18e8d86f6c1893eab6b1368fc14fa51339b Mon Sep 17 00:00:00 2001 From: eastargh Date: Thu, 4 Jun 2026 17:38:52 +0900 Subject: [PATCH 03/12] =?UTF-8?q?=EC=8A=A4=EC=9C=99=EC=B1=84=20&=20webhook?= =?UTF-8?q?=20=EB=B0=9C=EC=86=A1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rms/common/context/MonitoringContext.java | 4 + .../ext/djb/apistatus/ApiStatusService.java | 6 +- .../inflow/InflowTokenInsufficientEvent.java | 19 ----- .../ext/djb/inflow/InflowTokenService.java | 3 +- .../eai/rms/ext/djb/util/UmsManager.java | 8 +- .../djb/webhook/service/WebhookService.java | 56 ++++++++----- .../service/ums/UmsDispatchService.java | 84 ++++++++----------- 7 files changed, 82 insertions(+), 98 deletions(-) delete mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java diff --git a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java index 61cf3c1..f274bf3 100644 --- a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java +++ b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java @@ -254,6 +254,10 @@ public interface MonitoringContext { // 이중 로그인 허용여부 public static final String RMS_DUAL_LOGIN_ENABLED = "rms.DUAL_LOGIN_ENABLED"; + + // 웹훅 재전송 설정 + public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count"; + public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time"; diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java index 5bb77d0..885de37 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java @@ -76,12 +76,10 @@ public class ApiStatusService { String event = entry.getKey(); List apiIds = entry.getValue(); - Role role = new Role(); - role.setRoleId("api-monitor"); String message = getSwingChatMessage(event, apiIds); - ums.sendMessenger(role, MessageCode.API_STATUS_CHANGED, message); + ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message); - //제휴사 webhook 발송 + //제휴사 웹훅 발송 ums.sendWebhook(event, apiIds); //개발자포탈 API상태모니터링 화면을 위한 처리 diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java deleted file mode 100644 index dadcd80..0000000 --- a/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenInsufficientEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.eactive.eai.rms.ext.djb.inflow; - -import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class InflowTokenInsufficientEvent { - private final String eaisvcname; - private final String eaisvcdesc; - private final long cnt; - private final long rangeMinute; - - public static InflowTokenInsufficientEvent from(InflowTokenInsufficient summary, long rangeMinute) { - return new InflowTokenInsufficientEvent(summary.getEaisvcname(), summary.getEaisvcdesc(), summary.getCnt(), rangeMinute); - } -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java index fa7a12c..4859ee4 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/inflow/InflowTokenService.java @@ -5,11 +5,11 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.eai.rms.data.entity.man.role.Role; import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient; import com.eactive.eai.rms.ext.djb.util.UmsManager; @@ -32,6 +32,7 @@ public class InflowTokenService { for (InflowTokenInsufficient info : rows) { log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt()); String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname(); + ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message); } } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java b/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java index 0bb9d21..a33ccbf 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/util/UmsManager.java @@ -1,6 +1,5 @@ package com.eactive.eai.rms.ext.djb.util; -import java.security.SecureRandom; import java.util.HashMap; import java.util.List; @@ -36,10 +35,11 @@ public class UmsManager { * @param message * @return */ - public void sendMessenger(Role role, MessageCode messageCode, String message) { - List users = userInfoService.findAllByRoleId(role.getRoleId()); + public void sendMessenger(String roleId, MessageCode messageCode, String message) { + + List users = userInfoService.findAllByRoleId(roleId); if (users.isEmpty()) { - log.warn("sendMessenger: no users found for role '{}'", role.getRoleId()); + log.info("sendMessenger: no users found for role '{}'", roleId); return; } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java index 60f5c36..6a2ffc1 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java @@ -22,6 +22,7 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; +import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReq; import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqApi; import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqEvent; @@ -44,12 +45,14 @@ public class WebhookService { private final WebhookSendLogRepository sendLogRepository; private final ObjectMapper objectMapper; private final RestTemplate restTemplate; + private final MonitoringContext monitoringContext; @PersistenceContext(unitName = "entityManagerFactoryForEMS") private EntityManager entityManager; private static final String HMAC_ALGORITHM = "HmacSHA256"; - private static final int MAX_RETRY = 3; + private static final int DEFAULT_RETRY_COUNT = 3; + private static final int DEFAULT_RETRY_TIME = 10000; @SuppressWarnings("unchecked") @@ -119,18 +122,20 @@ public class WebhookService { headers.set("X-Webhook-Event", eventType); headers.set("X-Webhook-Timestamp", String.valueOf(webhookPayload.getTimestamp())); - // 4. 발송 + // 4. 발송 (재시도 포함) + int retryCount = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT); + int retryTime = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME); sendLog.setSentAt(LocalDateTime.now()); - HttpEntity request = new HttpEntity<>(payloadJson, headers); - ResponseEntity response = restTemplate.postForEntity(targetUrl, request, String.class); + HttpEntity httpRequest = new HttpEntity<>(payloadJson, headers); + ResponseEntity response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime); // 5. 성공 로그 sendLog.setStatusCode(response.getStatusCode().value()); sendLog.setResponseBody(response.getBody()); sendLog.setSuccess(response.getStatusCode().is2xxSuccessful()); - log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}", - eventType, targetUrl, response.getStatusCode()); + log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}", + eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount()); } catch (HttpClientErrorException | HttpServerErrorException e) { sendLog.setStatusCode(e.getStatusCode().value()); @@ -151,28 +156,33 @@ public class WebhookService { } + + /** - * 실패 건 재시도 - - @Transactional - public void retryFailedWebhooks() { - List failedLogs = - sendLogRepository.findFailedLogs(MAX_RETRY); - - for (WebhookSendLog failedLog : failedLogs) { + * 재시도 포함 HTTP 발송 + * - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요) + * - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 exponential backoff 재시도 (1s → 2s → 4s) + */ + private ResponseEntity sendWithRetry(String targetUrl, HttpEntity request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception { + Exception lastException = null; + for (int attempt = 0; attempt <= retryCount; attempt++) { try { - log.info("[Webhook] 재시도 - id: {}, retry: {}", failedLog.getId(), failedLog.getRetryCount()); - failedLog.setRetryCount(failedLog.getRetryCount() + 1); - sendLogRepository.save(failedLog); - - send(failedLog.getTargetUrl(), failedLog.getEventType(), - objectMapper.readValue(failedLog.getPayload(), Object.class)); - + if (attempt > 0) { + long delayMs = retryTime * (1L << (attempt - 1)); + log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs); + Thread.sleep(delayMs); + sendLog.setRetryCount(attempt); + } + return restTemplate.postForEntity(targetUrl, request, String.class); + } catch (HttpClientErrorException e) { + throw e; // 4xx는 재시도 불필요 } catch (Exception e) { - log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e); + lastException = e; + log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage()); } } - }*/ + throw lastException; + } /** * HMAC-SHA256 서명 생성 diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java index 8d3e521..d257a68 100644 --- a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java @@ -1,19 +1,5 @@ package com.eactive.eai.rms.onl.common.service.ums; -import com.eactive.apim.portal.template.entity.MessageRequest; -import com.eactive.apim.portal.template.repository.MessageRequestRepository; -import com.eactive.ext.kjb.ums.KjbEmailSendModule; -import com.eactive.ext.kjb.ums.KjbUmsService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Service; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionTemplate; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; @@ -21,6 +7,23 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.apim.portal.template.repository.MessageRequestRepository; +import com.eactive.ext.djb.ums.DjbMessengerService; +import com.eactive.ext.kjb.ums.KjbEmailSendModule; +import com.eactive.ext.kjb.ums.KjbUmsService; + +import lombok.extern.slf4j.Slf4j; @Slf4j @@ -67,6 +70,10 @@ public class UmsDispatchService { // KjbEmailSendModule.getInstance().setRepository(messageRequestRepository); log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton"); } + + private DjbMessengerService getMessengerService() { + return DjbMessengerService.getInstance(); + } private KjbUmsService getUmsService() { return KjbUmsService.getInstance(); @@ -131,54 +138,37 @@ public class UmsDispatchService { try { transactionTemplate.execute(status -> { try { - // 광주은행에서는 사용하지 않는 메소드 -// processMessage(message); - + if (DjbMessengerService.isAllowChannel(message)) { + getMessengerService().send(message); + } if (KjbUmsService.isAllowChannel(message)) { getUmsService().send(message); } - if (KjbEmailSendModule.isAllowChannel(message)) { getEmailModule().sendEmail(message); } - + updateMessageStatus(message, "SENT"); return null; } catch (Exception e) { - log.error("Failed to process message: {} - Error: {}", - message.getId(), e.getMessage(), e); - updateMessageStatus(message, "FAILED"); - status.setRollbackOnly(); - return null; + // checked exception을 RuntimeException으로 감싸 전파 → TransactionTemplate이 rollback 후 re-throw + throw new RuntimeException(e); } }); } catch (Exception e) { - log.error("Transaction execution failed for message: {} - Error: {}", - message.getId(), e.getMessage(), e); + log.error("Failed to process message: {} - Error: {}", message.getId(), e.getMessage(), e); + try { + transactionTemplate.execute(s -> { + updateMessageStatus(message, "FAILED"); + return null; + }); + } catch (Exception ex) { + log.error("Failed to update FAIL status for message: {}", message.getId(), ex); + } } }); } - /** - * kbank 방식으로 광주은행에서는 사용하지 않음 - * @param message - * @throws IOException - */ -// @Deprecated -// public void processMessage(MessageRequest message) throws IOException { -// try { -// String standardMessage = messageFactory.createMessage(message); -// log.debug("Processing message ID: {} - Type: {}", message.getId(), message.getMessageType()); -// -// umsSender.sendMessage(standardMessage); -// updateMessageStatus(message, "SENT"); -// log.debug("Successfully processed and sent message ID: {}", message.getId()); -// } catch (Exception e) { -// log.error("Message processing failed for ID: {} - Error: {}", -// message.getId(), e.getMessage(), e); -// updateMessageStatus(message, "FAILED"); -// throw e; -// } -// } + public void updateMessageStatus(MessageRequest message, String status) { message.setRequestStatus(status); From 7857f5b45cfc264bff606679cd8b41c6b79bdd87 Mon Sep 17 00:00:00 2001 From: eastargh Date: Thu, 4 Jun 2026 18:11:31 +0900 Subject: [PATCH 04/12] =?UTF-8?q?=EC=9E=90=EB=8F=99=20Log-off=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/jsp/common/include/script.jsp | 4 + WebContent/jsp/common/screen/main.jsp | 89 ++++++++++++++++++- .../rms/common/context/MonitoringContext.java | 3 + .../eai/rms/common/login/MainController.java | 2 + 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/WebContent/jsp/common/include/script.jsp b/WebContent/jsp/common/include/script.jsp index 1e96177..d4195f2 100644 --- a/WebContent/jsp/common/include/script.jsp +++ b/WebContent/jsp/common/include/script.jsp @@ -30,6 +30,10 @@ + <%-- --%> diff --git a/WebContent/jsp/common/screen/main.jsp b/WebContent/jsp/common/screen/main.jsp index 0642401..8748ef8 100644 --- a/WebContent/jsp/common/screen/main.jsp +++ b/WebContent/jsp/common/screen/main.jsp @@ -99,6 +99,93 @@ - + + <%-- 자동 로그아웃 경고 모달 --%> + + + + diff --git a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java index f274bf3..e9d6ed9 100644 --- a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java +++ b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java @@ -255,6 +255,9 @@ public interface MonitoringContext { // 이중 로그인 허용여부 public static final String RMS_DUAL_LOGIN_ENABLED = "rms.DUAL_LOGIN_ENABLED"; + // 자동 로그아웃 타임아웃 (단위: 분, 기본값: 10) + public static final String RMS_AUTO_LOGOUT_TIMEOUT = "rms.auto.logout.timeout"; + // 웹훅 재전송 설정 public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count"; public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time"; diff --git a/src/main/java/com/eactive/eai/rms/common/login/MainController.java b/src/main/java/com/eactive/eai/rms/common/login/MainController.java index 1471ec8..3994a19 100644 --- a/src/main/java/com/eactive/eai/rms/common/login/MainController.java +++ b/src/main/java/com/eactive/eai/rms/common/login/MainController.java @@ -617,6 +617,8 @@ public class MainController implements InterceptorSkipController { menuId = menuId == null ? "" : menuId; model.addAttribute("mainPage", mainPage); model.addAttribute("menuId", menuId); + model.addAttribute("autoLogoutTimeout", + monitoringContext.getIntProperty(MonitoringContext.RMS_AUTO_LOGOUT_TIMEOUT, 10)); if (logger.isDebugEnabled()) { logger.debug("CURRENT SERVICE TYPE : " + service); From feb4f845a840f43c923c764b7c7090b778c0d994 Mon Sep 17 00:00:00 2001 From: eastargh Date: Fri, 5 Jun 2026 10:11:21 +0900 Subject: [PATCH 05/12] =?UTF-8?q?=ED=8C=9D=EC=97=85=20=EB=91=90=EB=B2=88?= =?UTF-8?q?=EC=A7=B8=20=EC=9D=B4=ED=9B=84=20=EC=98=A4=ED=94=88=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/js/custom.js | 8 ++++++++ WebContent/jsp/js/custom.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/WebContent/js/custom.js b/WebContent/js/custom.js index e6bb511..c5e7c5f 100644 --- a/WebContent/js/custom.js +++ b/WebContent/js/custom.js @@ -36,6 +36,14 @@ // save the returnValue in options so that it is available in the callback function optns.returnValue = $frame[0].contentWindow.window.returnValue; optns.onClose(); + // Remove iframe from DOM so window.frames doesn't accumulate stale entries; + // without this, a second showModal call leaves frames[0] pointing to the first + // (closed) iframe, causing the previous returnValue to bleed into the next callback. + var $dialogEl = $frame.closest('.ui-dialog'); + setTimeout(function() { + $frame.remove(); + if ($dialogEl.length) $dialogEl.remove(); + }, 0); if(optns.option && optns.option.fullScreenPopup){ $('iframe[name=topFrame]',window.parent.document).css('display', 'block'); diff --git a/WebContent/jsp/js/custom.js b/WebContent/jsp/js/custom.js index 2cde935..5eda123 100644 --- a/WebContent/jsp/js/custom.js +++ b/WebContent/jsp/js/custom.js @@ -36,6 +36,14 @@ // save the returnValue in options so that it is available in the callback function optns.returnValue = $frame[0].contentWindow.window.returnValue; optns.onClose(); + // Remove iframe from DOM so window.frames doesn't accumulate stale entries; + // without this, a second showModal call leaves frames[0] pointing to the first + // (closed) iframe, causing the previous returnValue to bleed into the next callback. + var $dialogEl = $frame.closest('.ui-dialog'); + setTimeout(function() { + $frame.remove(); + if ($dialogEl.length) $dialogEl.remove(); + }, 0); if(optns.option && optns.option.fullScreenPopup){ $('iframe[name=topFrame]',window.parent.document).css('display', 'block'); From 480d7dac5a54b63dc6074144bf2bd97e98214d63 Mon Sep 17 00:00:00 2001 From: eastargh Date: Fri, 5 Jun 2026 15:10:06 +0900 Subject: [PATCH 06/12] =?UTF-8?q?=EC=95=B1=EC=8A=B9=EC=9D=B8=EC=8B=9C=20?= =?UTF-8?q?=EC=98=88=EC=83=81=EC=99=84=EB=A3=8C=EC=9D=BC=20=ED=95=AD?= =?UTF-8?q?=EB=AA=A9=20=EC=B6=94=EA=B0=80=20&=20=EC=8A=B9=EC=9D=B8?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EC=8B=9C=20"=EC=A7=84=ED=96=89=EC=A4=91"=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../approval/portalAppApprovalManPopup.jsp | 57 ++++++++++++++++++- .../onl/apim/approval/portalApprovalMan.jsp | 15 ++++- .../approval/PortalApprovalManController.java | 6 ++ .../approval/PortalApprovalManService.java | 17 ++++++ .../onl/apim/approval/PortalApprovalUI.java | 2 + 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp b/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp index ad28250..03e3996 100644 --- a/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp +++ b/WebContent/jsp/onl/apim/approval/portalAppApprovalManPopup.jsp @@ -38,12 +38,22 @@ var loginUser = '<%= loginVo.getUserId() %>'; var url = ''; var url_view = ''; + + function dateFormat(cellvalue) { + if ( cellvalue == null || cellvalue.length < 8 ) + return ''; + return cellvalue.substr(0 ,4) + '-' + + cellvalue.substr(4, 2) + '-' + + cellvalue.substr(6, 2) ; + } $(document).ready(function () { var key = "${param.id}"; if (key) { detail(key); } + + $("#expectEndDate").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true}); $("#btn_app_approve").click(function () { if (confirm("승인하시겠습니까?")) { @@ -112,6 +122,28 @@ }); } }); + + $("#btn_modify").click(function () { + if (confirm("수정하시겠습니까?")) { + $.ajax({ + type: "POST", + url: url, + data: {cmd: "UPDATE", id: key, expectEndDate: $("#expectEndDate").val().replaceAll('-','')}, + success: function (args) { + alert("수정되었습니다."); + if (window.dialogArguments && window.dialogArguments.self) { + window.dialogArguments.self.location.reload(); + } else if (window.opener) { + window.opener.location.reload(); + } + window.close(); + }, + error: function (e) { + alert(e.responseText); + } + }); + } + }); $("#btn_close").click(function () { window.close(); @@ -126,12 +158,25 @@ data: {cmd: 'DETAIL', id: key}, success: function (json) { var data = json; + + if (data.approvalStatus.code == 'END') { + $('#btn_modify').hide(); + $('#expectEndDate').hide(); + $('#spExpectEndDate').show(); + $('#spExpectEndDate').text(dateFormat(data.expectEndDate)); + } else { + $('#btn_modify').show(); + $('#expectEndDate').show(); + $('#spExpectEndDate').hide(); + } + $("#approvalType").text(data.approvalType === 'USER' ? "법인사용자" : "API 사용"); $("#approvalStatus").text(data.approvalStatus.description); $("#requesterName").text(data.requester.userName); $("#approvalSubject").text(data.approvalSubject); $("#approvalDetail").html(data.approvalDetail); $("#createdDate").text(data.createdDate).inputmask("9999-99-99 99:99:99.999", {'autoUnmask': true}); + $("#expectEndDate").val(data.expectEndDate); if (data.approvers.some(approver => approver.approverStatus === 'CURRENT' && @@ -144,7 +189,7 @@ document.getElementById("btn_app_reject").style.display = "none"; } - if (data.approvalStatus.description === '배포실패') { + if (data.approvalStatus.code === 'FAILED') { document.getElementById("btn_app_redeploy").style.display = ""; } @@ -242,9 +287,14 @@ 상세 내용 - +
+ 예상완료일 + + + + @@ -282,6 +332,9 @@