알림 발송 관련 배치 리네이밍

This commit is contained in:
eastargh
2026-05-08 17:24:17 +09:00
parent 4a1a4be54d
commit 133c05f1d7
19 changed files with 230 additions and 71 deletions
+12 -9
View File
@@ -9,15 +9,7 @@
<param-name>contextConfigLocation</param-name> <param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value> <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param> </context-param>
<!-- XSS Filter -->
<filter>
<filter-name>CrossScriptingFilter</filter-name>
<filter-class>com.eactive.eai.rms.common.filter.CrossScriptingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CrossScriptingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter> <filter>
<filter-name>encodingFilter</filter-name> <filter-name>encodingFilter</filter-name>
@@ -56,6 +48,17 @@
<filter-name>encodingFilter</filter-name> <filter-name>encodingFilter</filter-name>
<url-pattern>*.json</url-pattern> <url-pattern>*.json</url-pattern>
</filter-mapping> </filter-mapping>
<!-- XSS Filter -->
<filter>
<filter-name>CrossScriptingFilter</filter-name>
<filter-class>com.eactive.eai.rms.common.filter.CrossScriptingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CrossScriptingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- <!--
<filter> <filter>
@@ -30,12 +30,12 @@ public class ApiStatus extends AbstractEntity<String> {
private String statusCode; private String statusCode;
@LastModifiedBy // ← EMSAuditorAware가 자동으로 채움 @LastModifiedBy // ← EMSAuditorAware가 자동으로 채움
@Column(name = "MOD_USERID", length = 50) @Column(name = "MODIFIED_BY", length = 50)
private String modUserid = "SCHEDULER"; private String modifiedBy = "SCHEDULER";
@LastModifiedDate // ← save() 시 자동으로 sysdate 채움 @LastModifiedDate // ← save() 시 자동으로 sysdate 채움
@Column(name = "MOD_DATE") @Column(name = "MODIFIED_DATE")
private LocalDateTime modDate; private LocalDateTime modifiedDate;
@Override @Override
public @NonNull String getId() { return eaisvcname; } public @NonNull String getId() { return eaisvcname; }
@@ -2,5 +2,6 @@ package com.eactive.eai.rms.data.ext.djb.apistatus;
public interface ApiStatusEvent { public interface ApiStatusEvent {
String getEaisvcname(); String getEaisvcname();
String getEaisvcdesc();
String getEvent(); String getEvent();
} }
@@ -20,7 +20,9 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
* 7. 기타 : STAY * 7. 기타 : STAY
*/ */
@Query(nativeQuery = true, value = @Query(nativeQuery = true, value =
" SELECT EAISVCNAME, EVENT" " SELECT EAISVCNAME"
+ " , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC"
+ " , EVENT"
+ " FROM (" + " FROM ("
+ " SELECT EAISVCNAME" + " SELECT EAISVCNAME"
+ " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'" + " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'"
@@ -60,7 +62,7 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI'))) AS DELAY_YN" + " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI'))) AS DELAY_YN"
+ " FROM TSEAIHE01 A LEFT OUTER JOIN API_STATUS B ON A.EAISVCNAME = B.EAISVCNAME" + " FROM TSEAIHE01 A LEFT OUTER JOIN API_STATUS B ON A.EAISVCNAME = B.EAISVCNAME"
+ " )" + " )"
+ " ) WHERE EVENT != 'STAY'") + " ) A WHERE EVENT != 'STAY'")
List<ApiStatusEvent> findApiStatusEvents( List<ApiStatusEvent> findApiStatusEvents(
@Param("errorRate") int errorRate, @Param("errorRate") int errorRate,
@Param("errorRangeMinute") int errorRangeMinute, @Param("errorRangeMinute") int errorRangeMinute,
@@ -6,9 +6,12 @@ import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
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.ApiStatusChangedEvent;
@Service @Service
@Transactional @Transactional
public class ApiStatusService { public class ApiStatusService {
@@ -17,8 +20,12 @@ public class ApiStatusService {
@Autowired @Autowired
private ApiStatusRepository apiStatusRepository; private ApiStatusRepository apiStatusRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
public void updateApiStatus(HashMap<String, String> param) { public void updateApiStatus(HashMap<String, String> param) {
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents( List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
@@ -32,27 +39,28 @@ public class ApiStatusService {
String newStatusCode = resolveStatusCode(event.getEvent()); String newStatusCode = resolveStatusCode(event.getEvent());
if (newStatusCode == null) continue; if (newStatusCode == null) continue;
ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()) ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus());
.orElse(new ApiStatus());
apiStatus.setEaisvcname(event.getEaisvcname()); apiStatus.setEaisvcname(event.getEaisvcname());
apiStatus.setStatusCode(newStatusCode); apiStatus.setStatusCode(newStatusCode);
apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT
log.debug("API 상태 변경: {} {} → {}", event.getEaisvcname(), event.getEvent(), newStatusCode); log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode);
//TODO: 내부직원 알림발송 & 제휴사 webhook 발송 eventPublisher.publishEvent(ApiStatusChangedEvent.from(event)); // 트랜잭션 커밋 후 리스너 실행
log.debug("이벤트 전파");
} }
} }
private String resolveStatusCode(String event) { private String resolveStatusCode(String event) {
switch (event) { switch (event) {
case "CONTROL_START": return "C"; case "CONTROL_START": return "C"; //점검
case "CONTROL_END": return "N"; case "CONTROL_END": return "N"; //정상
case "ERROR_START": return "E"; case "ERROR_START": return "E"; //장애
case "ERROR_END": return "N"; case "ERROR_END": return "N"; //정상
case "DELAY_START": return "D"; case "DELAY_START": return "D"; //지연
case "DELAY_END": return "N"; case "DELAY_END": return "N"; //정상
default: default:
log.warn("알 수 없는 이벤트: {}", event); log.warn("알 수 없는 이벤트: {}", event);
return null; return null;
@@ -1,30 +0,0 @@
package com.eactive.eai.rms.data.ext.djb.inflow;
import java.util.List;
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;
@Service
@Transactional
public class InflowTokenFailService {
private static final Logger log = LoggerFactory.getLogger(InflowTokenFailService.class);
@Autowired
private InflowTokenFailRepository inflowTokenFailRepository;
public void checkRecentFails(long rangeMinute) {
List<InflowTokenFailSummary> rows = inflowTokenFailRepository.countByEaisvcname(rangeMinute);
for (InflowTokenFailSummary summary : rows) {
log.debug("유량제어 토큰 획득 실패: {} 최근 {}분 동안 {}건", summary.getEaisvcname(), rangeMinute, summary.getCnt());
//TOOD: 내부직원 알림 발송
}
}
}
@@ -1,6 +1,7 @@
package com.eactive.eai.rms.data.ext.djb.inflow; package com.eactive.eai.rms.data.ext.djb.inflow;
public interface InflowTokenFailSummary { public interface InflowTokenInsufficient {
String getEaisvcname(); String getEaisvcname();
String getEaisvcdesc();
Long getCnt(); Long getCnt();
} }
@@ -17,12 +17,12 @@ import lombok.NonNull;
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Entity @Entity
@Table(name = "TSEAIFR11") @Table(name = "TSEAIFR11")
public class InflowTokenFailLog extends AbstractEntity<InflowTokenFailLogId> implements Serializable { public class InflowTokenInsufficientLog extends AbstractEntity<InflowTokenInsufficientLogId> implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@EmbeddedId @EmbeddedId
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
private InflowTokenFailLogId id; private InflowTokenInsufficientLogId id;
@Column(name = "EAISEVRINSTNCNAME", length = 20) @Column(name = "EAISEVRINSTNCNAME", length = 20)
private String eaisevrinstncname; private String eaisevrinstncname;
@@ -43,5 +43,5 @@ public class InflowTokenFailLog extends AbstractEntity<InflowTokenFailLogId> imp
private String thresholdtimeunit; private String thresholdtimeunit;
@Override @Override
public @NonNull InflowTokenFailLogId getId() { return id; } public @NonNull InflowTokenInsufficientLogId getId() { return id; }
} }
@@ -7,7 +7,7 @@ package com.eactive.eai.rms.data.ext.djb.inflow;
@Data @Data
@Embeddable @Embeddable
public class InflowTokenFailLogId implements Serializable { public class InflowTokenInsufficientLogId implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "EAIBZWKDSTCD", length = 4) @Column(name = "EAIBZWKDSTCD", length = 4)
@@ -5,13 +5,14 @@ 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;
public interface InflowTokenFailRepository extends BaseRepository<InflowTokenFailLog, InflowTokenFailLogId> { public interface InflowTokenRepository extends BaseRepository<InflowTokenInsufficientLog, InflowTokenInsufficientLogId> {
@Query(nativeQuery = true, value = @Query(nativeQuery = true, value =
" SELECT EAISVCNAME" + " SELECT EAISVCNAME" +
" , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC" +
" , COUNT(*) AS CNT" + " , COUNT(*) AS CNT" +
" FROM TSEAIFR11" + " FROM TSEAIFR11 A" +
" WHERE MSGDPSTYMS >= TO_CHAR(SYSTIMESTAMP - NUMTODSINTERVAL(:rangeMinute, 'MINUTE'), 'YYYYMMDDHH24MISSFF3')" + " WHERE MSGDPSTYMS >= TO_CHAR(SYSTIMESTAMP - NUMTODSINTERVAL(:rangeMinute, 'MINUTE'), 'YYYYMMDDHH24MISSFF3')" +
" GROUP BY EAISVCNAME") " GROUP BY EAISVCNAME")
List<InflowTokenFailSummary> countByEaisvcname(@Param("rangeMinute") long rangeMinute); List<InflowTokenInsufficient> countTokenInsufficient(@Param("rangeMinute") long rangeMinute);
} }
@@ -0,0 +1,37 @@
package com.eactive.eai.rms.data.ext.djb.inflow;
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.InflowTokenInsufficientEvent;
@Service
@Transactional
public class InflowTokenService {
private static final Logger log = LoggerFactory.getLogger(InflowTokenService.class);
@Autowired
private InflowTokenRepository inflowTokenRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
public void checkRecentFails(long rangeMinute) {
List<InflowTokenInsufficient> rows = inflowTokenRepository.countTokenInsufficient(rangeMinute);
for (InflowTokenInsufficient info : rows) {
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
//내부직원 알림 발송
eventPublisher.publishEvent(InflowTokenInsufficientEvent.from(info, rangeMinute));
}
}
}
@@ -0,0 +1,25 @@
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;
}
}
@@ -0,0 +1,40 @@
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());
}
}
@@ -0,0 +1,18 @@
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());
}
}
@@ -0,0 +1,19 @@
package com.eactive.eai.rms.ext.djb.event;
import com.eactive.eai.rms.data.ext.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);
}
}
@@ -43,9 +43,9 @@ import com.eactive.eai.rms.onl.common.util.DateUtil;
* </pre> * </pre>
*/ */
@DisallowConcurrentExecution @DisallowConcurrentExecution
public class ApiStatusUpdateJob implements Job { public class ApiStatusMonitorJob implements Job {
private static final Logger log = LoggerFactory.getLogger(ApiStatusUpdateJob.class); private static final Logger log = LoggerFactory.getLogger(ApiStatusMonitorJob.class);
/** Job 파라미터 키: API 장애기준 시간간격(분) */ /** Job 파라미터 키: API 장애기준 시간간격(분) */
public static final String KEY_API_STATUS_ERROR_RANGE_MINUTE = "api.status.error.range_minute"; public static final String KEY_API_STATUS_ERROR_RANGE_MINUTE = "api.status.error.range_minute";
@@ -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.InflowTokenFailService; import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenService;
import com.eactive.eai.rms.onl.common.util.DateUtil; import com.eactive.eai.rms.onl.common.util.DateUtil;
/** /**
@@ -40,9 +40,9 @@ import com.eactive.eai.rms.onl.common.util.DateUtil;
*/ */
@DisallowConcurrentExecution @DisallowConcurrentExecution
public class InflowTokenFailMonitorJob implements Job { public class InflowTokenMonitorJob implements Job {
private static final Logger log = LoggerFactory.getLogger(InflowTokenFailMonitorJob.class); private static final Logger log = LoggerFactory.getLogger(InflowTokenMonitorJob.class);
private transient MonitoringContext monitoringContext; private transient MonitoringContext monitoringContext;
@@ -72,7 +72,7 @@ public class InflowTokenFailMonitorJob implements Job {
} }
monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext"); monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext");
InflowTokenFailService service = appContext.getBean(InflowTokenFailService.class); InflowTokenService service = appContext.getBean(InflowTokenService.class);
DataSourceContextHolder.setDataSourceType( DataSourceContextHolder.setDataSourceType(
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW)); DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
@@ -0,0 +1,17 @@
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,17 @@
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() 호출
}
}