Merge branch '알람기능_및_UMS_연동' into jenkins_with_weblogic
This commit is contained in:
@@ -6,6 +6,11 @@ import com.eactive.eai.common.lifecycle.LifecycleListener;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleSupport;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.custom.alarm.AlarmStateManager;
|
||||
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
import com.eactive.eai.custom.alarm.key.CoreAlarmKey;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
|
||||
@@ -71,6 +76,28 @@ public class CircuitBreakerManager implements Lifecycle {
|
||||
circuitBreakerRegistry = CircuitBreakerRegistry.of(config);
|
||||
|
||||
TaggedCircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry).bindTo(meterRegistry);
|
||||
|
||||
circuitBreakerRegistry.getEventPublisher().onEntryAdded(event -> {
|
||||
CircuitBreaker cb = event.getAddedEntry();
|
||||
String apiId = cb.getName();
|
||||
|
||||
// 여기서 상태 변경 리스너를 한 번만 등록
|
||||
cb.getEventPublisher().onStateTransition(stateEvent -> {
|
||||
CircuitBreaker.State from = stateEvent.getStateTransition().getFromState();
|
||||
CircuitBreaker.State to = stateEvent.getStateTransition().getToState();
|
||||
|
||||
AlarmEvent alarmEvent = AlarmEvent.builder().key(CoreAlarmKey.CircuitBreaker)
|
||||
.condition(AlarmCondition.builder().build())
|
||||
.message(String.format("CircuitBreaker state changed. apiId=%s, %s -> %s", apiId, from, to))
|
||||
.build();
|
||||
|
||||
AlarmStateManager.getAlarmStateManager().onEvnet(alarmEvent);
|
||||
|
||||
logger.warn("CircuitBreaker state changed. apiId={}, {} -> {}", apiId, from, to);
|
||||
});
|
||||
|
||||
logger.info("Registered StateTransition listener for: {}", apiId);
|
||||
});
|
||||
|
||||
logger.info("init CircuitBreaker - "+config);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.eactive.eai.common.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public final class Jsons {
|
||||
|
||||
private Jsons() {}
|
||||
|
||||
public static final Gson GSON = new Gson();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.eactive.eai.custom.alarm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Jsons;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
import com.eactive.eai.custom.alarm.key.DynamicAlarmKey;
|
||||
import com.eactive.eai.custom.alarm.policy.AlarmPolicy;
|
||||
import com.eactive.eai.custom.alarm.policy.CounterPolicy;
|
||||
import com.eactive.eai.custom.alarm.policy.SendPolicy;
|
||||
import com.eactive.eai.custom.alarm.policy.TimerPolicy;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@Service
|
||||
public class AlarmService {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
final static String ALARM_GROUP = "Alarm";
|
||||
final static String ALARM_KEY = "{ALARM}";
|
||||
final static String ALARM_RECIVER = "{RECIVER}";
|
||||
final static String ALARM_EABLE = "enable.alarm";
|
||||
|
||||
public Map<AlarmKey, AlarmPolicy> getAlarmPolicy() {
|
||||
|
||||
Map<AlarmKey, AlarmPolicy> policyMap = new HashMap<AlarmKey, AlarmPolicy>();
|
||||
|
||||
try {
|
||||
|
||||
Properties props = PropManager.getInstance().getProperties(ALARM_GROUP);
|
||||
|
||||
props.forEach((key, value) -> {
|
||||
|
||||
String keyName = (String) key;
|
||||
String configStr = (String) value;
|
||||
|
||||
if (keyName.contains(ALARM_KEY)) {
|
||||
|
||||
AlarmPolicy newPolicy = buildAlarmPolicy(keyName, configStr);
|
||||
policyMap.put(newPolicy.getAlarmKey(), newPolicy);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("getAlarmPolicy fail.", e);
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
return policyMap;
|
||||
}
|
||||
|
||||
public boolean isAlarmEnabled() {
|
||||
return Optional.ofNullable(PropManager.getInstance().getProperty(ALARM_GROUP, ALARM_EABLE))
|
||||
.map(Boolean::valueOf).orElse(false);
|
||||
}
|
||||
|
||||
private AlarmPolicy buildAlarmPolicy(String key, String configJsonStr) {
|
||||
|
||||
JsonObject alarmConfig = Jsons.GSON.fromJson(configJsonStr, JsonObject.class);
|
||||
|
||||
String policyType = alarmConfig.get("type").getAsString();
|
||||
int thresholdCount = Optional.ofNullable(alarmConfig.get("thresholdCount")).map(i -> i.getAsInt()).orElse(5);
|
||||
int timeWindowSec = Optional.ofNullable(alarmConfig.get("timeWindowSec")).map(i -> i.getAsInt()).orElse(60);
|
||||
|
||||
AlarmPolicy alarmPolicy;
|
||||
|
||||
if ("count".equals(policyType)) {
|
||||
alarmPolicy = CounterPolicy.builder().alarmKey(new DynamicAlarmKey(key)).thresholdCount(thresholdCount)
|
||||
.build();
|
||||
} else {
|
||||
alarmPolicy = TimerPolicy.builder().alarmKey(new DynamicAlarmKey(key)).timeWindowMs(timeWindowSec * 1000)
|
||||
.build();
|
||||
}
|
||||
|
||||
return alarmPolicy;
|
||||
|
||||
}
|
||||
|
||||
public Map<AlarmKey, SendPolicy> getSendPolicy() {
|
||||
|
||||
Map<AlarmKey, SendPolicy> policyMap = new HashMap<AlarmKey, SendPolicy>();
|
||||
|
||||
try {
|
||||
|
||||
Properties props = PropManager.getInstance().getProperties(ALARM_GROUP);
|
||||
|
||||
props.forEach((key, value) -> {
|
||||
String keyName = (String) key;
|
||||
String configStr = (String) value;
|
||||
|
||||
if (!keyName.contains(ALARM_RECIVER))
|
||||
return;
|
||||
|
||||
SendPolicy newPolicy = buildSendPolicy(keyName, configStr);
|
||||
|
||||
policyMap.put(newPolicy.getAlarmKey(), newPolicy);
|
||||
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("getSendPolicy fail.", e);
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
return policyMap;
|
||||
}
|
||||
|
||||
private SendPolicy buildSendPolicy(String key, String configStr) {
|
||||
|
||||
List<String> reciverList = Optional.ofNullable(configStr).map(t -> Arrays.asList(t.split(",")))
|
||||
.orElse(Collections.EMPTY_LIST);
|
||||
|
||||
key = key.replace(ALARM_RECIVER, "").trim() + "{ALARM}";
|
||||
|
||||
SendPolicy sendPolicy = new SendPolicy(key, reciverList);
|
||||
|
||||
return sendPolicy;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.eactive.eai.custom.alarm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
import com.eactive.eai.custom.alarm.policy.AlarmPolicy;
|
||||
import com.eactive.eai.custom.alarm.policy.SendPolicy;
|
||||
import com.eactive.eai.custom.alarm.ums.UmsService;
|
||||
import com.eactive.eai.custom.alarm.ums.payload.ObpMonitoringUmsTemplate;
|
||||
import com.eactive.eai.custom.alarm.ums.payload.UmsRequestPayload;
|
||||
|
||||
@Component
|
||||
public class AlarmStateManager {
|
||||
|
||||
List<AlarmEvent> registerAlarmList = new ArrayList<AlarmEvent>();
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
private static AlarmStateManager alarmStateManager;
|
||||
|
||||
@Autowired
|
||||
AlarmService alarmService;
|
||||
|
||||
@Autowired
|
||||
UmsService usmService;
|
||||
|
||||
public static AlarmStateManager getAlarmStateManager() {
|
||||
if (alarmStateManager == null) {
|
||||
alarmStateManager = ApplicationContextProvider.getContext().getBean(AlarmStateManager.class);
|
||||
}
|
||||
return alarmStateManager;
|
||||
}
|
||||
|
||||
public AlarmStateManager() {
|
||||
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("AlarmStateManager-worker");
|
||||
t.setDaemon(false);
|
||||
return t;
|
||||
});
|
||||
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
|
||||
try {
|
||||
// === 여기에 지속 실행할 로직 ===
|
||||
fireAlarm();
|
||||
|
||||
} catch (Throwable e) {
|
||||
// 반드시 예외 잡기 (안 잡으면 스케줄 자체가 죽음)
|
||||
logger.error("fireAlarm fail", e);
|
||||
}
|
||||
|
||||
}, 0, 5, TimeUnit.SECONDS); // 최초 0초, 이후 5초 간격
|
||||
}
|
||||
|
||||
public void onEvnet(AlarmEvent alaramEvent) {
|
||||
|
||||
Optional<AlarmEvent> existEvent = registerAlarmList.stream().filter(e -> e.equals(alaramEvent)).findFirst();
|
||||
|
||||
if (existEvent.isPresent()) {
|
||||
AlarmEvent event = existEvent.get();
|
||||
event.onOcurr();
|
||||
} else {
|
||||
registerAlarmList.add(alaramEvent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void fireAlarm() {
|
||||
|
||||
Map<AlarmKey, AlarmPolicy> alarmPolicyMap = alarmService.getAlarmPolicy();
|
||||
|
||||
Map<AlarmKey, SendPolicy> sendPolicyMap = alarmService.getSendPolicy();
|
||||
|
||||
Iterator<AlarmEvent> iterator = registerAlarmList.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
AlarmEvent event = iterator.next();
|
||||
AlarmPolicy alarmPolicy = alarmPolicyMap.get(event.getKey());
|
||||
|
||||
if (alarmPolicy != null && alarmPolicy.isAlarmTriggered(event)) {
|
||||
// 알람 발송 로직
|
||||
SendPolicy sendPolicy = sendPolicyMap.get(event.getKey());
|
||||
if (alarmService.isAlarmEnabled()) {
|
||||
send(sendPolicy, event);
|
||||
} else {
|
||||
logger.info("Alarm is disabled, event = {}, policy = {}", event, sendPolicy);
|
||||
}
|
||||
// 리스트에서 안전하게 제거
|
||||
iterator.remove();
|
||||
}else { //트리거 조건이 안되더라도, 이벤트발생한지 오래 되었으면 제거하여 OOM 예방.
|
||||
|
||||
|
||||
long firstOcurredAt = event.getCondition().getOcurredAt();
|
||||
|
||||
long diffInMs = System.currentTimeMillis() - firstOcurredAt;
|
||||
|
||||
if ( diffInMs >= TimeUnit.MINUTES.toMillis(60) ) { //1시간 지난거면 삭제
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void send(SendPolicy sendPolicy, AlarmEvent event) {
|
||||
|
||||
UmsRequestPayload payload = null;
|
||||
|
||||
try {
|
||||
|
||||
List<String> cellPhoneList = sendPolicy.getCellPhoneList();
|
||||
|
||||
for (String cellPhone : cellPhoneList) {
|
||||
|
||||
payload = UmsRequestPayload.builder()
|
||||
.content(ObpMonitoringUmsTemplate.builder().message(event.getMessage()).build())
|
||||
.cellphone(cellPhone).build();
|
||||
|
||||
usmService.send(payload);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(String.format("Alarm message delivery failed (UMS), payload=%s", payload), e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.eactive.eai.custom.alarm.condition;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
@ToString
|
||||
public class AlarmCondition {
|
||||
final long ocurredAt = System.currentTimeMillis();
|
||||
long lastOcurredAt; // 일단, 미사용
|
||||
@Builder.Default
|
||||
long count = 1;
|
||||
|
||||
public void onOcurr() {
|
||||
this.lastOcurredAt = System.currentTimeMillis();
|
||||
this.count++;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.eactive.eai.custom.alarm.condition;
|
||||
|
||||
public enum AlarmLevel {
|
||||
ERROR, WARN, FATAL
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.eactive.eai.custom.alarm.event;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
import com.eactive.eai.custom.alarm.policy.AlarmPolicy;
|
||||
import com.eactive.eai.custom.alarm.policy.CounterPolicy;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@Builder
|
||||
@ToString
|
||||
public class AlarmEvent {
|
||||
AlarmKey key;
|
||||
AlarmCondition condition;
|
||||
String message;
|
||||
|
||||
public void onOcurr() {
|
||||
condition.onOcurr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof AlarmEvent)) return false;
|
||||
|
||||
AlarmEvent that = (AlarmEvent) o;
|
||||
return Objects.equals(this.key, that.key)
|
||||
&& Objects.equals(this.message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.eactive.eai.custom.alarm.key;
|
||||
|
||||
|
||||
public interface AlarmKey {
|
||||
String code();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eactive.eai.custom.alarm.key;
|
||||
|
||||
public final class CoreAlarmKey {
|
||||
|
||||
private CoreAlarmKey() { }
|
||||
|
||||
public static final AlarmKey CircuitBreaker = new DynamicAlarmKey("CircuitBreaker{ALARM}");
|
||||
|
||||
public static final AlarmKey Timeout = new DynamicAlarmKey("Timeout{ALARM}");
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.eactive.eai.custom.alarm.key;
|
||||
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
public final class DynamicAlarmKey implements AlarmKey {
|
||||
|
||||
private final String code;
|
||||
|
||||
public DynamicAlarmKey(String code) {
|
||||
if (code == null || code.isEmpty()) {
|
||||
throw new IllegalArgumentException("AlarmKey code must not be empty");
|
||||
}
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof AlarmKey)) return false;
|
||||
return code.equals(((AlarmKey) o).code());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return code.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.eactive.eai.custom.alarm.policy;
|
||||
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
|
||||
public abstract class AlarmPolicy {
|
||||
|
||||
private AlarmKey alarmKey;
|
||||
|
||||
private int thresholdCount;
|
||||
|
||||
private long timeWindowMs;
|
||||
|
||||
protected AlarmPolicy(Builder<?> builder) {
|
||||
this.alarmKey = builder.alarmKey;
|
||||
this.thresholdCount = builder.thresholdCount;
|
||||
this.timeWindowMs = builder.timeWindowMs;
|
||||
}
|
||||
|
||||
public AlarmKey getAlarmKey() {
|
||||
return alarmKey;
|
||||
}
|
||||
|
||||
public int getThresholdCount() {
|
||||
return thresholdCount;
|
||||
}
|
||||
|
||||
public long getTimeWindowMs() {
|
||||
return timeWindowMs;
|
||||
}
|
||||
|
||||
public abstract boolean isAlarmTriggered(AlarmEvent event);
|
||||
|
||||
|
||||
public boolean hasElapsed(long baseTimeMs) {
|
||||
|
||||
return System.currentTimeMillis() - baseTimeMs >= timeWindowMs;
|
||||
|
||||
}
|
||||
|
||||
public boolean hasExceededLimit(long count) {
|
||||
return count >= thresholdCount;
|
||||
}
|
||||
|
||||
public abstract static class Builder<T extends Builder<T>> {
|
||||
|
||||
private AlarmKey alarmKey;
|
||||
private int thresholdCount;
|
||||
private long timeWindowMs;
|
||||
|
||||
public T alarmKey(AlarmKey alarmKey) {
|
||||
this.alarmKey = alarmKey;
|
||||
return self();
|
||||
}
|
||||
|
||||
public T thresholdCount(int thresholdCount) {
|
||||
this.thresholdCount = thresholdCount;
|
||||
return self();
|
||||
}
|
||||
|
||||
public T timeWindowMs(long timeWindowMs) {
|
||||
this.timeWindowMs = timeWindowMs;
|
||||
return self();
|
||||
}
|
||||
|
||||
protected abstract T self();
|
||||
|
||||
abstract AlarmPolicy build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.eactive.eai.custom.alarm.policy;
|
||||
|
||||
public enum AlarmType {
|
||||
COUNT, TIMER
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.eactive.eai.custom.alarm.policy;
|
||||
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
|
||||
public class CounterPolicy extends AlarmPolicy {
|
||||
|
||||
private CounterPolicy(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlarmTriggered(AlarmEvent event) {
|
||||
|
||||
if (!event.getKey().equals(getAlarmKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long count = event.getCondition().getCount();
|
||||
return hasExceededLimit(count);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder extends AlarmPolicy.Builder<Builder> {
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CounterPolicy build() {
|
||||
return new CounterPolicy(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.eactive.eai.custom.alarm.policy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
import com.eactive.eai.custom.alarm.key.DynamicAlarmKey;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
@ToString
|
||||
@Getter
|
||||
public class SendPolicy {
|
||||
|
||||
final AlarmKey alarmKey;
|
||||
|
||||
final List<String> cellPhoneList;
|
||||
|
||||
public SendPolicy(String alarmKey, List<String> cellPhoneList) {
|
||||
this.alarmKey = new DynamicAlarmKey(alarmKey);
|
||||
this.cellPhoneList = cellPhoneList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.eactive.eai.custom.alarm.policy;
|
||||
|
||||
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
import com.eactive.eai.custom.alarm.policy.CounterPolicy.Builder;
|
||||
|
||||
public class TimerPolicy extends AlarmPolicy {
|
||||
|
||||
private TimerPolicy(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlarmTriggered(AlarmEvent event) {
|
||||
|
||||
if (event.getKey().equals(getAlarmKey())) {
|
||||
|
||||
AlarmCondition condition = event.getCondition();
|
||||
|
||||
long baseTimeMs = condition.getOcurredAt();
|
||||
|
||||
if (hasElapsed(baseTimeMs)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder extends AlarmPolicy.Builder<Builder> {
|
||||
|
||||
@Override
|
||||
protected Builder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimerPolicy build() {
|
||||
return new TimerPolicy(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.eactive.eai.custom.alarm.ums;
|
||||
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class UmsCallResult {
|
||||
private String umsMessageId;
|
||||
private boolean isSuccess;
|
||||
|
||||
private String returnCode;
|
||||
private String returnMessage;
|
||||
private String returnPayload;
|
||||
|
||||
private Throwable throwable;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.eactive.eai.custom.alarm.ums;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.custom.alarm.ums.payload.UmsRequestPayload;
|
||||
|
||||
@Component
|
||||
public class UmsSendManager {
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
Queue<UmsRequestPayload> queue = new ConcurrentLinkedQueue<UmsRequestPayload>();
|
||||
|
||||
@Autowired
|
||||
UmsService umsService;
|
||||
|
||||
public UmsSendManager() {
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("AlarmStateManager-worker");
|
||||
t.setDaemon(false);
|
||||
return t;
|
||||
});
|
||||
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
|
||||
try {
|
||||
// === 여기에 지속 실행할 로직 ===
|
||||
sendMessage();
|
||||
|
||||
} catch (Throwable e) {
|
||||
// 반드시 예외 잡기 (안 잡으면 스케줄 자체가 죽음)
|
||||
logger.error("fireAlarm fail", e);
|
||||
}
|
||||
|
||||
}, 0, 5, TimeUnit.SECONDS); // 최초 0초, 이후 5초 간격
|
||||
}
|
||||
|
||||
void sendMessage() throws Exception {
|
||||
UmsRequestPayload payload;
|
||||
while ((payload = queue.poll()) != null) {
|
||||
umsService.send(payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void enqueue(UmsRequestPayload payload) {
|
||||
queue.offer(payload);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.eactive.eai.custom.alarm.ums;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.entity.EntityBuilder;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.core5.http.ContentType;
|
||||
import org.apache.hc.core5.http.ProtocolException;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Jsons;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.custom.alarm.ums.payload.UmsRequestPayload;
|
||||
import com.eactive.eai.custom.alarm.ums.payload.ValidationUtil;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
@Component
|
||||
public class UmsService {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
private final static String UMS_GROUP_PROP_NAME = "UMS";
|
||||
private final static String UMS_PROP_HOST = "host";
|
||||
private final static String UMS_PROP_URL = "url";
|
||||
private final static String UMS_PROP_API_KEY = "apiKey";
|
||||
private final static String UMS_URL = "http://172.31.35.144:9021";
|
||||
private final static String API_KEY = "7cca6d58-e4f7-474d-9edd-525806bc99ff";
|
||||
private final static String SEND_URL = "/ums/openapi/send";
|
||||
private final static String CONTENT_TYPE = "application/json; charset=utf-8";
|
||||
private final static String AUTHORIZATION_FMT = "Bearer %s";
|
||||
|
||||
public Future<UmsCallResult> send(UmsRequestPayload payload) throws Exception {
|
||||
|
||||
if (payload.getContent() == null) {
|
||||
throw new IllegalArgumentException("content is null");
|
||||
}
|
||||
|
||||
try {
|
||||
ValidationUtil.validateOrThrow(payload);
|
||||
} catch (Exception ex) {
|
||||
logger.debug(String.format("UMS Request payload validation failed - %s", payload));
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return call(payload);
|
||||
}
|
||||
|
||||
private CloseableHttpClient createHttpClient() {
|
||||
return HttpClients.createDefault();
|
||||
}
|
||||
|
||||
private Future<UmsCallResult> call(UmsRequestPayload requestPayload) throws Exception {
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
String json = Jsons.GSON.toJson(requestPayload);
|
||||
|
||||
String host = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_HOST, UMS_URL);
|
||||
String url = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_URL, SEND_URL);
|
||||
String apiKey = PropManager.getInstance().getProperty(UMS_GROUP_PROP_NAME, UMS_PROP_API_KEY, API_KEY);
|
||||
|
||||
HttpPost httpPost = new HttpPost(host + url);
|
||||
httpPost.setHeader("Content-Type", CONTENT_TYPE);
|
||||
httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, apiKey));
|
||||
httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build());
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
try {
|
||||
logger.debug("Header - {}", httpPost.getHeader("Content-Type"));
|
||||
logger.debug("Header - {}", httpPost.getHeader("Authorization"));
|
||||
logger.debug("Request Body : {}",
|
||||
StreamUtils.copyToString(httpPost.getEntity().getContent(), Charset.defaultCharset()));
|
||||
} catch (ProtocolException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
httpPost.setConfig(RequestConfig.custom().setConnectTimeout(Timeout.ofSeconds(3))
|
||||
.setResponseTimeout(Timeout.ofSeconds(3)).build());
|
||||
Future<UmsCallResult> future;
|
||||
try (CloseableHttpClient httpClient = createHttpClient()) {
|
||||
future = executor.submit(() -> {
|
||||
return httpClient.execute(httpPost, res -> {
|
||||
try {
|
||||
UmsCallResult result;
|
||||
String body = EntityUtils.toString(res.getEntity());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Response - {}", body);
|
||||
}
|
||||
result = Jsons.GSON.fromJson(body, UmsCallResult.class);
|
||||
result.setUmsMessageId(requestPayload.getMessageIdentityNo());
|
||||
result.setSuccess(true);
|
||||
return result;
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error("UMS Call error(JSON Syntax)", e);
|
||||
return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo())
|
||||
.isSuccess(false).throwable(e).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("UMS Call error", e);
|
||||
return UmsCallResult.builder().umsMessageId(requestPayload.getMessageIdentityNo())
|
||||
.isSuccess(false).throwable(e).build();
|
||||
} finally {
|
||||
logger.debug("Response status - {}", res.getCode());
|
||||
|
||||
if (res.getCode() != 200) {
|
||||
Arrays.asList(res.getHeaders()).forEach(o -> {
|
||||
logger.warn("[Header] {}: {}", o.getName(), o.getValue());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return future;
|
||||
} catch (Exception e) {
|
||||
logger.error("UMS call exception", e);
|
||||
throw new Exception("UMS call failed", e);
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.eactive.eai.custom.alarm.ums.payload;
|
||||
|
||||
/**
|
||||
* GSON Serialize 용 Object
|
||||
*/
|
||||
public interface IUmsGsonObject {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eactive.eai.custom.alarm.ums.payload;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class ObpMonitoringUmsTemplate implements IUmsGsonObject {
|
||||
@SerializedName("MOTR_EVAL_CNTN")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eactive.eai.custom.alarm.ums.payload;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum UmsBizWorkCode {
|
||||
MONITORING("B_ELFN_09510", "OBP 모니터링"),
|
||||
VERIFY_PHONE("S55_ICT_0006", "API Portal 인증번호"),
|
||||
;
|
||||
|
||||
@Getter
|
||||
private String code;
|
||||
@Getter
|
||||
private String displayName;
|
||||
|
||||
UmsBizWorkCode(String code, String displayName) {
|
||||
this.code = code;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.eactive.eai.custom.alarm.ums.payload;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Getter
|
||||
public class UmsRequestPayload {
|
||||
// MSG_IDNT_NO string 메시지식별번호 50 O "발송 구분을 위한 거래 번호 (GUID 권고)
|
||||
//GUID로 입력이 어려울시
|
||||
//어플리케이션코드(3자리)+난수 로 유니크한 ID로 채번 권고"
|
||||
@Length(min = 1, max = 50)
|
||||
@NotEmpty
|
||||
@SerializedName("MSG_IDNT_NO")
|
||||
@Builder.Default
|
||||
private String messageIdentityNo = String.format("APM-%s",DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(LocalDateTime.now()));
|
||||
|
||||
// MSG_BZWK_CD string 메시지업무코드 30 O UMS에 등록된 메시지업무코드(AS-IS 템플릿ID)
|
||||
@NotEmpty
|
||||
@Length(min = 1, max = 30)
|
||||
@SerializedName("MSG_BZWK_CD")
|
||||
@Builder.Default
|
||||
private String msgBizWorkCode = UmsBizWorkCode.MONITORING.getCode();
|
||||
|
||||
// 메시지업무파라미터내용 / 4000 / JSONObject
|
||||
// MSG_BZWK_PARAM_CTNT object 메시지업무파라미터내용 4000 메시지업무코드의 템플릿에서 사용하는 변수와 변수값
|
||||
@NotNull
|
||||
@SerializedName("MSG_BZWK_PARAM_CTNT")
|
||||
private IUmsGsonObject content;
|
||||
|
||||
// 발송예약일시 / 14 / 실 발송 일자(yyyyMMddHHmmss)
|
||||
// SND_RESR_DTTM string 발송예약일시 14 20240418111500 O 실 발송 일자(현재시간)
|
||||
@Pattern(regexp = "^[0-9]{14}$")
|
||||
@NotEmpty
|
||||
@SerializedName("SND_RESR_DTTM")
|
||||
@Builder.Default
|
||||
private String sendReservationDatetime = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
|
||||
|
||||
// 휴대전화번호 / 44 / 휴대폰번호(평문, 암호화 둘다가능)
|
||||
// CPNO string 휴대전화번호 44 "암호화 또는 평문 (safedb not rrno)
|
||||
//문자일 시 암호문
|
||||
//숫자일 시 전화번호
|
||||
//고객번호 또는 휴대전화번호 필수"
|
||||
@NotEmpty
|
||||
@Pattern(regexp = "^[0-9]{10,11}$")
|
||||
@SerializedName("CPNO")
|
||||
private String cellphone;
|
||||
|
||||
// 발송부점코드 / 4 /
|
||||
// SND_BRCD string 발송부점코드 4 O 0333
|
||||
@NotEmpty
|
||||
@SerializedName("SND_BRCD")
|
||||
@Builder.Default
|
||||
private String sendBranchCode = "0990";
|
||||
|
||||
// 발송직원번호 / 7 / ECEB057(텔러번호?)
|
||||
// SND_EMPNO string 발송직원번호 7 O ? 넣긴해야함
|
||||
@NotEmpty
|
||||
@SerializedName("SND_EMPNO")
|
||||
@Builder.Default
|
||||
private String sendEmployeeNo = "ECEB057";
|
||||
|
||||
// SRVC_ID string 서비스ID 11 요청단 측 서비스ID
|
||||
@NotEmpty
|
||||
@SerializedName("SRVC_ID")
|
||||
@Builder.Default
|
||||
private String serviceId = "eAPIM";
|
||||
|
||||
// MSG_SND_CHNL_CD string 메시지발송채널코드 2
|
||||
@SerializedName("MSG_SND_CHNL_CD")
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
private ChannelCode msgSndChnlCd = ChannelCode.KM;
|
||||
|
||||
// VALDTN_RSLT_CD string 검증결과코드 2 00으로 고정
|
||||
@SerializedName("VALDTN_RSLT_CD")
|
||||
@NotEmpty
|
||||
@Builder.Default
|
||||
private String validationResultCode = "00";
|
||||
|
||||
public enum ChannelCode {
|
||||
SM, // SMS
|
||||
LM, // LMS
|
||||
MM, // MMS
|
||||
KM, // 카카오-알림톡
|
||||
FM, // 카카오-친구톡
|
||||
SR, // RCS-SMS
|
||||
LR, // RCS-LMS
|
||||
MR, // RCS-MMS
|
||||
TR, // RCS-TEMPLATE
|
||||
IR, // RCS-IMAGE Template
|
||||
PM // Push
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.eactive.eai.custom.alarm.ums.payload;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
|
||||
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
|
||||
|
||||
public class ValidationUtil {
|
||||
private static final ValidatorFactory factory;
|
||||
private static final Validator validator;
|
||||
|
||||
static {
|
||||
factory = Validation.byDefaultProvider()
|
||||
.configure()
|
||||
.messageInterpolator(new ParameterMessageInterpolator())
|
||||
.buildValidatorFactory();
|
||||
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
|
||||
private ValidationUtil() {
|
||||
}
|
||||
|
||||
|
||||
public static <T> Set<ConstraintViolation<T>> validate(T target) {
|
||||
return validator.validate(target);
|
||||
}
|
||||
|
||||
public static <T> void validateOrThrow(T target) {
|
||||
Set<ConstraintViolation<T>> violations = validate(target);
|
||||
if (!violations.isEmpty()) {
|
||||
String message = violations.stream()
|
||||
.map(v -> v.getPropertyPath() + " : " + v.getMessage())
|
||||
.collect(Collectors.joining(", "));
|
||||
throw new ConstraintViolationException("Validation failed: " + message, violations);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> String getViolationMessage(T target) {
|
||||
Set<ConstraintViolation<T>> violations = validate(target);
|
||||
if (violations.isEmpty()) return null;
|
||||
return violations.stream()
|
||||
.map(v -> v.getPropertyPath() + " : " + v.getMessage())
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eactive.eai.custom.alarm.ums.payload;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class VerifyPhoneUmsTemplate implements IUmsGsonObject {
|
||||
@SerializedName("ATHN_NO")
|
||||
private String authNumber;
|
||||
}
|
||||
@@ -42,6 +42,11 @@ import com.eactive.eai.common.worker.ResponseMap;
|
||||
import com.eactive.eai.control.HttpSender;
|
||||
import com.eactive.eai.control.LogSender;
|
||||
import com.eactive.eai.control.Transform;
|
||||
import com.eactive.eai.custom.alarm.AlarmStateManager;
|
||||
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
import com.eactive.eai.custom.alarm.key.CoreAlarmKey;
|
||||
import com.eactive.eai.env.ElinkConfig;
|
||||
import com.eactive.eai.inbound.processor.Processor;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
@@ -96,20 +101,24 @@ public class RESTProcess extends HTTPProcess {
|
||||
String circuitBreakerUseYn = PropManager.getInstance().getProperty("CircuitBreaker", "useYn");
|
||||
|
||||
try {
|
||||
if("Y".equals(circuitBreakerUseYn)){
|
||||
String apiId = reqEaiMsg.getEAISvcCd();
|
||||
CircuitBreaker circuitBreaker = CircuitBreakerManager.getInstance().getCircuitBreaker(apiId);
|
||||
if (true) {
|
||||
String apiId = reqEaiMsg.getEAISvcCd();
|
||||
CircuitBreaker circuitBreaker = CircuitBreakerManager.getInstance().getCircuitBreaker(apiId);
|
||||
|
||||
// ObjectMapper 인스턴스 생성
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// ObjectMapper 인스턴스 생성
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 서킷 브레이커의 메트릭 정보와 상태 정보를 직접 Node로 변환
|
||||
ObjectNode combinedNode = objectMapper.createObjectNode();
|
||||
combinedNode.put("state", circuitBreaker.getState().toString());
|
||||
combinedNode.set("metrics", objectMapper.valueToTree(circuitBreaker.getMetrics()));
|
||||
// 서킷 브레이커의 메트릭 정보와 상태 정보를 직접 Node로 변환
|
||||
ObjectNode combinedNode = objectMapper.createObjectNode();
|
||||
combinedNode.put("state", circuitBreaker.getState().toString());
|
||||
combinedNode.set("metrics", objectMapper.valueToTree(circuitBreaker.getMetrics()));
|
||||
|
||||
logger.info("CircuitBreaker state " + combinedNode.toString());
|
||||
this.resObject = circuitBreaker.executeCallable(() -> HttpSender
|
||||
.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp));
|
||||
|
||||
|
||||
|
||||
logger.info("CircuitBreaker state "+combinedNode.toString());
|
||||
this.resObject = circuitBreaker.executeCallable(() -> HttpSender.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp));
|
||||
}else{
|
||||
this.resObject = HttpSender.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user