스윙채 & webhook 발송 수정
This commit is contained in:
@@ -254,6 +254,10 @@ public interface MonitoringContext {
|
||||
|
||||
// 이중 로그인 허용여부
|
||||
public static final String RMS_DUAL_LOGIN_ENABLED = "rms.DUAL_LOGIN_ENABLED";
|
||||
|
||||
// 웹훅 재전송 설정
|
||||
public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count";
|
||||
public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -76,12 +76,10 @@ public class ApiStatusService {
|
||||
String event = entry.getKey();
|
||||
List<String> apiIds = entry.getValue();
|
||||
|
||||
Role role = new Role();
|
||||
role.setRoleId("api-monitor");
|
||||
String message = getSwingChatMessage(event, apiIds);
|
||||
ums.sendMessenger(role, MessageCode.API_STATUS_CHANGED, message);
|
||||
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message);
|
||||
|
||||
//제휴사 webhook 발송
|
||||
//제휴사 웹훅 발송
|
||||
ums.sendWebhook(event, apiIds);
|
||||
|
||||
//개발자포탈 API상태모니터링 화면을 위한 처리
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.inflow;
|
||||
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class InflowTokenInsufficientEvent {
|
||||
private final String eaisvcname;
|
||||
private final String eaisvcdesc;
|
||||
private final long cnt;
|
||||
private final long rangeMinute;
|
||||
|
||||
public static InflowTokenInsufficientEvent from(InflowTokenInsufficient summary, long rangeMinute) {
|
||||
return new InflowTokenInsufficientEvent(summary.getEaisvcname(), summary.getEaisvcdesc(), summary.getCnt(), rangeMinute);
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.eai.rms.data.entity.man.role.Role;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
||||
import com.eactive.eai.rms.ext.djb.util.UmsManager;
|
||||
|
||||
@@ -32,6 +32,7 @@ public class InflowTokenService {
|
||||
for (InflowTokenInsufficient info : rows) {
|
||||
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
||||
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
||||
|
||||
ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.eactive.eai.rms.ext.djb.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@@ -36,10 +35,11 @@ public class UmsManager {
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public void sendMessenger(Role role, MessageCode messageCode, String message) {
|
||||
List<UserInfo> users = userInfoService.findAllByRoleId(role.getRoleId());
|
||||
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 '{}'", role.getRoleId());
|
||||
log.info("sendMessenger: no users found for role '{}'", roleId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReq;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqApi;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqEvent;
|
||||
@@ -44,12 +45,14 @@ public class WebhookService {
|
||||
private final WebhookSendLogRepository sendLogRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RestTemplate restTemplate;
|
||||
private final MonitoringContext monitoringContext;
|
||||
|
||||
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
|
||||
private EntityManager entityManager;
|
||||
|
||||
private static final String HMAC_ALGORITHM = "HmacSHA256";
|
||||
private static final int MAX_RETRY = 3;
|
||||
private static final int DEFAULT_RETRY_COUNT = 3;
|
||||
private static final int DEFAULT_RETRY_TIME = 10000;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -119,18 +122,20 @@ public class WebhookService {
|
||||
headers.set("X-Webhook-Event", eventType);
|
||||
headers.set("X-Webhook-Timestamp", String.valueOf(webhookPayload.getTimestamp()));
|
||||
|
||||
// 4. 발송
|
||||
// 4. 발송 (재시도 포함)
|
||||
int retryCount = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT);
|
||||
int retryTime = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
||||
sendLog.setSentAt(LocalDateTime.now());
|
||||
HttpEntity<String> request = new HttpEntity<>(payloadJson, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(targetUrl, request, String.class);
|
||||
HttpEntity<String> httpRequest = new HttpEntity<>(payloadJson, headers);
|
||||
ResponseEntity<String> response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||
|
||||
// 5. 성공 로그
|
||||
sendLog.setStatusCode(response.getStatusCode().value());
|
||||
sendLog.setResponseBody(response.getBody());
|
||||
sendLog.setSuccess(response.getStatusCode().is2xxSuccessful());
|
||||
|
||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}",
|
||||
eventType, targetUrl, response.getStatusCode());
|
||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}",
|
||||
eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||
|
||||
} catch (HttpClientErrorException | HttpServerErrorException e) {
|
||||
sendLog.setStatusCode(e.getStatusCode().value());
|
||||
@@ -151,28 +156,33 @@ public class WebhookService {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 실패 건 재시도
|
||||
|
||||
@Transactional
|
||||
public void retryFailedWebhooks() {
|
||||
List<WebhookSendLog> failedLogs =
|
||||
sendLogRepository.findFailedLogs(MAX_RETRY);
|
||||
|
||||
for (WebhookSendLog failedLog : failedLogs) {
|
||||
* 재시도 포함 HTTP 발송
|
||||
* - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요)
|
||||
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 exponential backoff 재시도 (1s → 2s → 4s)
|
||||
*/
|
||||
private ResponseEntity<String> sendWithRetry(String targetUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||
Exception lastException = null;
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++) {
|
||||
try {
|
||||
log.info("[Webhook] 재시도 - id: {}, retry: {}", failedLog.getId(), failedLog.getRetryCount());
|
||||
failedLog.setRetryCount(failedLog.getRetryCount() + 1);
|
||||
sendLogRepository.save(failedLog);
|
||||
|
||||
send(failedLog.getTargetUrl(), failedLog.getEventType(),
|
||||
objectMapper.readValue(failedLog.getPayload(), Object.class));
|
||||
|
||||
if (attempt > 0) {
|
||||
long delayMs = retryTime * (1L << (attempt - 1));
|
||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs);
|
||||
Thread.sleep(delayMs);
|
||||
sendLog.setRetryCount(attempt);
|
||||
}
|
||||
return restTemplate.postForEntity(targetUrl, request, String.class);
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw e; // 4xx는 재시도 불필요
|
||||
} catch (Exception e) {
|
||||
log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e);
|
||||
lastException = e;
|
||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
throw lastException;
|
||||
}
|
||||
|
||||
/**
|
||||
* HMAC-SHA256 서명 생성
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
package com.eactive.eai.rms.onl.common.service.ums;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.apim.portal.template.repository.MessageRequestRepository;
|
||||
import com.eactive.ext.kjb.ums.KjbEmailSendModule;
|
||||
import com.eactive.ext.kjb.ums.KjbUmsService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -21,6 +7,23 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.apim.portal.template.repository.MessageRequestRepository;
|
||||
import com.eactive.ext.djb.ums.DjbMessengerService;
|
||||
import com.eactive.ext.kjb.ums.KjbEmailSendModule;
|
||||
import com.eactive.ext.kjb.ums.KjbUmsService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@@ -67,6 +70,10 @@ public class UmsDispatchService {
|
||||
// KjbEmailSendModule.getInstance().setRepository(messageRequestRepository);
|
||||
log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton");
|
||||
}
|
||||
|
||||
private DjbMessengerService getMessengerService() {
|
||||
return DjbMessengerService.getInstance();
|
||||
}
|
||||
|
||||
private KjbUmsService getUmsService() {
|
||||
return KjbUmsService.getInstance();
|
||||
@@ -131,54 +138,37 @@ public class UmsDispatchService {
|
||||
try {
|
||||
transactionTemplate.execute(status -> {
|
||||
try {
|
||||
// 광주은행에서는 사용하지 않는 메소드
|
||||
// processMessage(message);
|
||||
|
||||
if (DjbMessengerService.isAllowChannel(message)) {
|
||||
getMessengerService().send(message);
|
||||
}
|
||||
if (KjbUmsService.isAllowChannel(message)) {
|
||||
getUmsService().send(message);
|
||||
}
|
||||
|
||||
if (KjbEmailSendModule.isAllowChannel(message)) {
|
||||
getEmailModule().sendEmail(message);
|
||||
}
|
||||
|
||||
updateMessageStatus(message, "SENT");
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process message: {} - Error: {}",
|
||||
message.getId(), e.getMessage(), e);
|
||||
updateMessageStatus(message, "FAILED");
|
||||
status.setRollbackOnly();
|
||||
return null;
|
||||
// checked exception을 RuntimeException으로 감싸 전파 → TransactionTemplate이 rollback 후 re-throw
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Transaction execution failed for message: {} - Error: {}",
|
||||
message.getId(), e.getMessage(), e);
|
||||
log.error("Failed to process message: {} - Error: {}", message.getId(), e.getMessage(), e);
|
||||
try {
|
||||
transactionTemplate.execute(s -> {
|
||||
updateMessageStatus(message, "FAILED");
|
||||
return null;
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed to update FAIL status for message: {}", message.getId(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* kbank 방식으로 광주은행에서는 사용하지 않음
|
||||
* @param message
|
||||
* @throws IOException
|
||||
*/
|
||||
// @Deprecated
|
||||
// public void processMessage(MessageRequest message) throws IOException {
|
||||
// try {
|
||||
// String standardMessage = messageFactory.createMessage(message);
|
||||
// log.debug("Processing message ID: {} - Type: {}", message.getId(), message.getMessageType());
|
||||
//
|
||||
// umsSender.sendMessage(standardMessage);
|
||||
// updateMessageStatus(message, "SENT");
|
||||
// log.debug("Successfully processed and sent message ID: {}", message.getId());
|
||||
// } catch (Exception e) {
|
||||
// log.error("Message processing failed for ID: {} - Error: {}",
|
||||
// message.getId(), e.getMessage(), e);
|
||||
// updateMessageStatus(message, "FAILED");
|
||||
// throw e;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public void updateMessageStatus(MessageRequest message, String status) {
|
||||
message.setRequestStatus(status);
|
||||
|
||||
Reference in New Issue
Block a user