api 상태 변화 감지 & 웹훅 발송
This commit is contained in:
@@ -69,6 +69,7 @@
|
|||||||
<context:component-scan base-package="com.eactive.apim.portal" />
|
<context:component-scan base-package="com.eactive.apim.portal" />
|
||||||
|
|
||||||
<context:component-scan base-package="com.eactive.ext.kjb.statistics" />
|
<context:component-scan base-package="com.eactive.ext.kjb.statistics" />
|
||||||
|
<context:component-scan base-package="com.eactive.ext.djb" />
|
||||||
|
|
||||||
|
|
||||||
<bean id="cacheManager"
|
<bean id="cacheManager"
|
||||||
|
|||||||
@@ -107,6 +107,17 @@ public class UserInfoService extends
|
|||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<UserInfo> 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<UserInfo> findByUsername(Pageable pageable, String username) {
|
public Page<UserInfo> findByUsername(Pageable pageable, String username) {
|
||||||
QUserInfo qUserInfo = QUserInfo.userInfo;
|
QUserInfo qUserInfo = QUserInfo.userInfo;
|
||||||
BooleanExpression predicate = qUserInfo.isNotNull();
|
BooleanExpression predicate = qUserInfo.isNotNull();
|
||||||
|
|||||||
+1
-1
@@ -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;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
+2
-1
@@ -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 {
|
public interface ApiStatusEvent {
|
||||||
String getEaisvcname();
|
String getEaisvcname();
|
||||||
String getEaisvcdesc();
|
String getEaisvcdesc();
|
||||||
|
String getPrevStatusCode();
|
||||||
String getEvent();
|
String getEvent();
|
||||||
}
|
}
|
||||||
+1
-1
@@ -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 {
|
public interface InflowTokenInsufficient {
|
||||||
String getEaisvcname();
|
String getEaisvcname();
|
||||||
+1
-1
@@ -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 java.io.Serializable;
|
||||||
|
|
||||||
+1
-1
@@ -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 java.io.Serializable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
+1
-1
@@ -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.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -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<String, String> param) {
|
|
||||||
|
|
||||||
List<ApiStatusEvent> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-8
@@ -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 java.util.List;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import com.eactive.eai.data.jpa.BaseRepository;
|
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<ApiStatus, String> {
|
public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||||
|
|
||||||
@@ -22,9 +24,11 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
|||||||
@Query(nativeQuery = true, value =
|
@Query(nativeQuery = true, value =
|
||||||
" SELECT EAISVCNAME"
|
" SELECT EAISVCNAME"
|
||||||
+ " , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC"
|
+ " , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC"
|
||||||
|
+ " , PREV_STATUS_CODE "
|
||||||
+ " , EVENT"
|
+ " , EVENT"
|
||||||
+ " FROM ("
|
+ " FROM ("
|
||||||
+ " SELECT EAISVCNAME"
|
+ " SELECT EAISVCNAME"
|
||||||
|
+ " , STATUS_CODE AS PREV_STATUS_CODE "
|
||||||
+ " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'"
|
+ " , 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 = 'C' AND CTRL_YN = 'N' THEN 'CONTROL_END'"
|
||||||
+ " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'"
|
+ " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'"
|
||||||
@@ -34,14 +38,14 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
|||||||
+ " ELSE 'STAY' END AS EVENT"
|
+ " ELSE 'STAY' END AS EVENT"
|
||||||
+ " FROM ("
|
+ " FROM ("
|
||||||
+ " SELECT A.EAISVCNAME"
|
+ " SELECT A.EAISVCNAME"
|
||||||
+ " , NVL(B.STATUS_CODE, '1') AS STATUS_CODE"
|
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
||||||
+ " , NVL((SELECT 'Y' FROM TSEAITI01"
|
+ " , NVL(( SELECT 'Y' FROM EMSAPP.PTL_NOTICE_API X "
|
||||||
+ " WHERE (TO_CHAR(SYSDATE,'HH24MI') BETWEEN SUBSTR(EAICTRLDSTICCTNT,16,4) AND SUBSTR(EAICTRLDSTICCTNT,21,4)"
|
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.PTL_NOTICE Y WHERE X.NOTICE_ID = Y.ID "
|
||||||
+ " OR SUBSTR(EAICTRLDSTICCTNT,16,9) = '0000|0000')"
|
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDDHH24MI') BETWEEN START_TIME AND END_TIME) "
|
||||||
+ " AND A.EAISVCNAME = EAICTRLNAME),'N') AS CTRL_YN"
|
+ " AND A.EAISVCNAME = X.API_NAME),'N') AS CTRL_YN "
|
||||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||||
+ " ELSE 'N' END"
|
+ " ELSE 'N' END"
|
||||||
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL"
|
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL"
|
||||||
+ " , SUM(SUCCESS_CNT) AS SUCCESS"
|
+ " , SUM(SUCCESS_CNT) AS SUCCESS"
|
||||||
+ " FROM API_STATS_MINUTE"
|
+ " FROM API_STATS_MINUTE"
|
||||||
@@ -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<String, String> param) {
|
||||||
|
|
||||||
|
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
|
||||||
|
Integer.parseInt(param.get("errorRate")),
|
||||||
|
Integer.parseInt(param.get("errorRangeMinute")),
|
||||||
|
Integer.parseInt(param.get("delayRangeMinute")),
|
||||||
|
Integer.parseInt(param.get("delayAvgRespTime"))
|
||||||
|
);
|
||||||
|
|
||||||
|
HashMap<String, List<String>> 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<String> apis = (List<String>)eventMap.get(event.getEvent());
|
||||||
|
if (apis == null) {
|
||||||
|
apis = new ArrayList<String>();
|
||||||
|
apis.add(event.getEaisvcname());
|
||||||
|
eventMap.put(event.getEvent(), apis);
|
||||||
|
} else {
|
||||||
|
apis.add(event.getEaisvcname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//이벤트별로 ums, webhook을 발송한다
|
||||||
|
for (Map.Entry<String, List<String>> entry : eventMap.entrySet()) {
|
||||||
|
String event = entry.getKey();
|
||||||
|
List<String> 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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -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.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
+4
-1
@@ -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 java.util.List;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import com.eactive.eai.data.jpa.BaseRepository;
|
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<InflowTokenInsufficientLog, InflowTokenInsufficientLogId> {
|
public interface InflowTokenRepository extends BaseRepository<InflowTokenInsufficientLog, InflowTokenInsufficientLogId> {
|
||||||
|
|
||||||
+7
-6
@@ -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;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -9,7 +9,9 @@ import org.springframework.context.ApplicationEventPublisher;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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
|
@Service
|
||||||
@@ -22,16 +24,15 @@ public class InflowTokenService {
|
|||||||
private InflowTokenRepository inflowTokenRepository;
|
private InflowTokenRepository inflowTokenRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ApplicationEventPublisher eventPublisher;
|
private UmsManager ums;
|
||||||
|
|
||||||
public void checkRecentFails(long rangeMinute) {
|
public void checkRecentFails(long rangeMinute) {
|
||||||
List<InflowTokenInsufficient> rows = inflowTokenRepository.countTokenInsufficient(rangeMinute);
|
List<InflowTokenInsufficient> rows = inflowTokenRepository.countTokenInsufficient(rangeMinute);
|
||||||
|
|
||||||
for (InflowTokenInsufficient info : rows) {
|
for (InflowTokenInsufficient info : rows) {
|
||||||
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
||||||
|
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
||||||
//내부직원 알림 발송
|
ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message);
|
||||||
eventPublisher.publishEvent(InflowTokenInsufficientEvent.from(info, rangeMinute));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.DataSourceContextHolder;
|
||||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
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;
|
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,11 +88,11 @@ public class ApiStatusMonitorJob implements Job {
|
|||||||
monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext");
|
monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext");
|
||||||
ApiStatusService apiStatusUpdateService = appContext.getBean(ApiStatusService.class);
|
ApiStatusService apiStatusUpdateService = appContext.getBean(ApiStatusService.class);
|
||||||
|
|
||||||
|
|
||||||
DataSourceContextHolder.setDataSourceType(
|
DataSourceContextHolder.setDataSourceType(
|
||||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
|
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
|
||||||
try {
|
try {
|
||||||
apiStatusUpdateService.updateApiStatus(param);
|
apiStatusUpdateService.updateApiStatus(param);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("ApiStatusUpdateJob execution failed", e);
|
log.error("ApiStatusUpdateJob execution failed", e);
|
||||||
throw new JobExecutionException(e);
|
throw new JobExecutionException(e);
|
||||||
|
|||||||
@@ -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.DataSourceContextHolder;
|
||||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
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;
|
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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: 알림 발송 로직
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<UserInfo> 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<String, Object> params = new HashMap<>();
|
||||||
|
params.put("message", message);
|
||||||
|
|
||||||
|
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* event를 구둑중인 제휴사에게 웹훅 발송
|
||||||
|
* @param event
|
||||||
|
* @param apiIds
|
||||||
|
*/
|
||||||
|
public void sendWebhook(String event, List<String> apiIds) {
|
||||||
|
|
||||||
|
List<WebhookSendRequest> list = webhookService.findSendList(event, apiIds);
|
||||||
|
for (WebhookSendRequest req : list) {
|
||||||
|
webhookService.send(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-9
@@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
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.dto.WebhookSendRequest;
|
||||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookReceiveService;
|
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookReceiveService;
|
||||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService;
|
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService;
|
||||||
@@ -38,16 +38,12 @@ public class WebhookSendController {
|
|||||||
public ResponseEntity<Map<String, Object>> sendWebhook(
|
public ResponseEntity<Map<String, Object>> sendWebhook(
|
||||||
@RequestBody WebhookSendRequest request) {
|
@RequestBody WebhookSendRequest request) {
|
||||||
|
|
||||||
WebhookSendLog result = webhookService.send(
|
webhookService.send(request);
|
||||||
request.getTargetUrl(),
|
|
||||||
request.getEventType(),
|
|
||||||
request.getData()
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("logId", result.getId());
|
// response.put("logId", result.getId());
|
||||||
response.put("success", result.getSuccess());
|
// response.put("success", result.getSuccess());
|
||||||
response.put("statusCode", result.getStatusCode());
|
// response.put("statusCode", result.getStatusCode());
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import lombok.Setter;
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class WebhookSendRequest {
|
public class WebhookSendRequest {
|
||||||
|
private String orgId;
|
||||||
private String targetUrl;
|
private String targetUrl;
|
||||||
private String eventType;
|
private String eventType;
|
||||||
|
private String secret;
|
||||||
private Object data;
|
private Object data;
|
||||||
}
|
}
|
||||||
+11
@@ -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<WebhookReq, String> {
|
||||||
|
}
|
||||||
+1
-1
@@ -10,7 +10,7 @@ import org.springframework.data.repository.query.Param;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import com.eactive.eai.rms.data.EMSDataSource;
|
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
|
@Repository
|
||||||
@EMSDataSource
|
@EMSDataSource
|
||||||
|
|||||||
@@ -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() 호출
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,16 @@ package com.eactive.eai.rms.ext.djb.webhook.service;
|
|||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
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.HttpServerErrorException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
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.WebhookPayload;
|
||||||
|
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest;
|
||||||
import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository;
|
import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.querydsl.core.Tuple;
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -35,17 +45,58 @@ public class WebhookService {
|
|||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
|
||||||
private String secretKey = "HW8JtFpkPmQqsVmr0Rb81P4qDypaNIVbGf3ZNMMPfyerhOHshoKABLaMBo1HA4BldTxhw1NjYmVnoPu9IIV7cyRXjecL3b2UctyR7DnVaJausltZLLr8Qm4Bzs4wRmgf";
|
private EntityManager entityManager;
|
||||||
|
|
||||||
private static final String HMAC_ALGORITHM = "HmacSHA256";
|
private static final String HMAC_ALGORITHM = "HmacSHA256";
|
||||||
private static final int MAX_RETRY = 3;
|
private static final int MAX_RETRY = 3;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<WebhookSendRequest> findSendList(String event, List<String> apiIds) {
|
||||||
|
QWebhookReq req = QWebhookReq.webhookReq;
|
||||||
|
QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent;
|
||||||
|
QWebhookReqApi api = QWebhookReqApi.webhookReqApi;
|
||||||
|
|
||||||
|
List<Tuple> 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<String, WebhookSendRequest> 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<String>());
|
||||||
|
return wr;
|
||||||
|
});
|
||||||
|
((List<String>) resultMap.get(key).getData()).add(t.get(api.id.apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(resultMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 웹훅 발송 메인 메서드
|
* 웹훅 발송 메인 메서드
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@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();
|
WebhookSendLog sendLog = new WebhookSendLog();
|
||||||
sendLog.setTargetUrl(targetUrl);
|
sendLog.setTargetUrl(targetUrl);
|
||||||
sendLog.setEventType(eventType);
|
sendLog.setEventType(eventType);
|
||||||
@@ -57,7 +108,7 @@ public class WebhookService {
|
|||||||
sendLog.setPayload(payloadJson);
|
sendLog.setPayload(payloadJson);
|
||||||
|
|
||||||
// 2. Secret Key로 HMAC-SHA256 서명 생성
|
// 2. Secret Key로 HMAC-SHA256 서명 생성
|
||||||
String signature = generateSignature(payloadJson);
|
String signature = generateSignature(secret, payloadJson);
|
||||||
sendLog.setSignature(signature);
|
sendLog.setSignature(signature);
|
||||||
|
|
||||||
// 3. HTTP 요청 헤더 구성
|
// 3. HTTP 요청 헤더 구성
|
||||||
@@ -95,12 +146,13 @@ public class WebhookService {
|
|||||||
eventType, targetUrl, e.getMessage());
|
eventType, targetUrl, e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendLogRepository.save(sendLog);
|
sendLogRepository.save(sendLog);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 실패 건 재시도
|
* 실패 건 재시도
|
||||||
*/
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void retryFailedWebhooks() {
|
public void retryFailedWebhooks() {
|
||||||
List<WebhookSendLog> failedLogs =
|
List<WebhookSendLog> failedLogs =
|
||||||
@@ -119,16 +171,16 @@ public class WebhookService {
|
|||||||
log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e);
|
log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMAC-SHA256 서명 생성
|
* HMAC-SHA256 서명 생성
|
||||||
* Java 17 미만은 HexFormat 미지원이므로 직접 변환
|
* 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);
|
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
|
||||||
SecretKeySpec keySpec = new SecretKeySpec(
|
SecretKeySpec keySpec = new SecretKeySpec(
|
||||||
secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
|
secret.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
|
||||||
mac.init(keySpec);
|
mac.init(keySpec);
|
||||||
byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
|
byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
|
||||||
return bytesToHex(hash);
|
return bytesToHex(hash);
|
||||||
|
|||||||
@@ -68,14 +68,14 @@ public class UmsDispatchService {
|
|||||||
log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton");
|
log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton");
|
||||||
}
|
}
|
||||||
|
|
||||||
// private KjbUmsService getUmsService() {
|
private KjbUmsService getUmsService() {
|
||||||
// return KjbUmsService.getInstance();
|
return KjbUmsService.getInstance();
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private KjbEmailSendModule getEmailModule() {
|
private KjbEmailSendModule getEmailModule() {
|
||||||
// return KjbEmailSendModule.getInstance();
|
return KjbEmailSendModule.getInstance();
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
public void processMessages() {
|
public void processMessages() {
|
||||||
@@ -112,7 +112,7 @@ public class UmsDispatchService {
|
|||||||
|
|
||||||
log.info("Starting to submit {} messages for processing", messagesToProcess.size());
|
log.info("Starting to submit {} messages for processing", messagesToProcess.size());
|
||||||
for (MessageRequest message : messagesToProcess) {
|
for (MessageRequest message : messagesToProcess) {
|
||||||
//submitMessageTask(message);
|
submitMessageTask(message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("No pending messages found");
|
log.info("No pending messages found");
|
||||||
@@ -123,40 +123,40 @@ public class UmsDispatchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// private void submitMessageTask(MessageRequest message) {
|
private void submitMessageTask(MessageRequest message) {
|
||||||
// int queueSize = executorService.getQueue().size();
|
int queueSize = executorService.getQueue().size();
|
||||||
// log.debug("Current queue size before submission: {}", queueSize);
|
log.debug("Current queue size before submission: {}", queueSize);
|
||||||
//
|
|
||||||
// executorService.submit(() -> {
|
executorService.submit(() -> {
|
||||||
// try {
|
try {
|
||||||
// transactionTemplate.execute(status -> {
|
transactionTemplate.execute(status -> {
|
||||||
// try {
|
try {
|
||||||
// // 광주은행에서는 사용하지 않는 메소드
|
// 광주은행에서는 사용하지 않는 메소드
|
||||||
//// processMessage(message);
|
// processMessage(message);
|
||||||
//
|
|
||||||
// if (KjbUmsService.isAllowChannel(message)) {
|
if (KjbUmsService.isAllowChannel(message)) {
|
||||||
// getUmsService().send(message);
|
getUmsService().send(message);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// if (KjbEmailSendModule.isAllowChannel(message)) {
|
if (KjbEmailSendModule.isAllowChannel(message)) {
|
||||||
// getEmailModule().sendEmail(message);
|
getEmailModule().sendEmail(message);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// return null;
|
return null;
|
||||||
// } catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// log.error("Failed to process message: {} - Error: {}",
|
log.error("Failed to process message: {} - Error: {}",
|
||||||
// message.getId(), e.getMessage(), e);
|
message.getId(), e.getMessage(), e);
|
||||||
// updateMessageStatus(message, "FAILED");
|
updateMessageStatus(message, "FAILED");
|
||||||
// status.setRollbackOnly();
|
status.setRollbackOnly();
|
||||||
// return null;
|
return null;
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// } catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// log.error("Transaction execution failed for message: {} - Error: {}",
|
log.error("Transaction execution failed for message: {} - Error: {}",
|
||||||
// message.getId(), e.getMessage(), e);
|
message.getId(), e.getMessage(), e);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kbank 방식으로 광주은행에서는 사용하지 않음
|
* kbank 방식으로 광주은행에서는 사용하지 않음
|
||||||
|
|||||||
Reference in New Issue
Block a user