웹훅 발송 내역
This commit is contained in:
@@ -26,9 +26,12 @@ public class WebhookSendLog {
|
||||
)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "TARGET_URL", nullable = false, length = 500)
|
||||
@Column(name = "TARGET_URL", length = 500)
|
||||
private String targetUrl;
|
||||
|
||||
@Column(name = "PROXY_URL", length = 500)
|
||||
private String proxyUrl;
|
||||
|
||||
@Column(name = "EVENT_TYPE", length = 100)
|
||||
private String eventType;
|
||||
|
||||
@@ -66,6 +69,9 @@ public class WebhookSendLog {
|
||||
@Column(name = "SENT_AT")
|
||||
private LocalDateTime sentAt;
|
||||
|
||||
@Column(name = "SEND_BY", length = 100)
|
||||
private String sendBy;
|
||||
|
||||
/* Boolean 편의 메서드 */
|
||||
public void setSuccess(Boolean success) {
|
||||
this.success = (success != null && success) ? "Y" : "N";
|
||||
|
||||
@@ -20,6 +20,7 @@ public class WebhookSendLogUI {
|
||||
/** 모니터링 공통코드(EVENT_TYPE) 코드명. 상세 조회에서만 채워진다(목록은 화면에서 콤보로 변환). */
|
||||
private String eventTypeName;
|
||||
private String targetUrl;
|
||||
private String proxyUrl;
|
||||
|
||||
private String payload;
|
||||
private String signature;
|
||||
@@ -28,6 +29,7 @@ public class WebhookSendLogUI {
|
||||
private String responseBody;
|
||||
|
||||
private Boolean success;
|
||||
private String sendBy;
|
||||
private String errorMessage;
|
||||
private Integer retryCount;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public class WebhookSendLogUISearch {
|
||||
/* 성공여부 (Y/N, 공백=전체) */
|
||||
private String searchSuccess;
|
||||
|
||||
/* 등록일시 기간 검색 (yyyyMMdd, 8자리 — userLoginHistoryMan.jsp 기간검색 스타일) */
|
||||
/* 발송일시 기간 검색 (yyyyMMdd, 8자리 — userLoginHistoryMan.jsp 기간검색 스타일) */
|
||||
private String searchStartDate;
|
||||
private String searchEndDate;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import lombok.Setter;
|
||||
public class WebhookSendRequest {
|
||||
private String orgId;
|
||||
private String targetUrl;
|
||||
private String proxyUrl;
|
||||
private String eventType;
|
||||
private String secret;
|
||||
private String reverseProxyPath;
|
||||
|
||||
+4
-2
@@ -80,10 +80,10 @@ public class WebhookSendLogManService extends BaseService {
|
||||
predicate.and(log.success.eq(search.getSearchSuccess()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchStartDate())) {
|
||||
predicate.and(log.createdAt.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay()));
|
||||
predicate.and(log.sentAt.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchEndDate())) {
|
||||
predicate.and(log.createdAt.loe(parseYYYYMMDD(search.getSearchEndDate()).atTime(23, 59, 59)));
|
||||
predicate.and(log.sentAt.loe(parseYYYYMMDD(search.getSearchEndDate()).atTime(23, 59, 59)));
|
||||
}
|
||||
|
||||
Page<WebhookSendLog> page = webhookSendLogRepository.findAll(predicate, pageable);
|
||||
@@ -154,11 +154,13 @@ public class WebhookSendLogManService extends BaseService {
|
||||
ui.setEventType(e.getEventType());
|
||||
ui.setEventTypeName(eventTypeName);
|
||||
ui.setTargetUrl(e.getTargetUrl());
|
||||
ui.setProxyUrl(e.getProxyUrl());
|
||||
ui.setPayload(e.getPayload());
|
||||
ui.setSignature(e.getSignature());
|
||||
ui.setStatusCode(e.getStatusCode());
|
||||
ui.setResponseBody(e.getResponseBody());
|
||||
ui.setSuccess(e.getSuccess());
|
||||
ui.setSendBy(e.getSendBy());
|
||||
ui.setErrorMessage(e.getErrorMessage());
|
||||
ui.setRetryCount(e.getRetryCount());
|
||||
ui.setCreatedAt(fmt(e.getCreatedAt(), FMT_DATETIME));
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.eactive.apim.portal.portalorg.entity.QPortalOrg;
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.util.ContainerUtil;
|
||||
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;
|
||||
@@ -82,7 +83,8 @@ public class WebhookService {
|
||||
resultMap.computeIfAbsent(key, k -> {
|
||||
WebhookSendRequest wr = new WebhookSendRequest();
|
||||
wr.setOrgId(t.get(req.orgId));
|
||||
wr.setTargetUrl(this.convertTargetUrl(reverseProxyUrl, t.get(org.reverseProxyPath), t.get(req.targetUrl)));
|
||||
wr.setTargetUrl(t.get(req.targetUrl));
|
||||
wr.setProxyUrl(this.getProxyUrl(reverseProxyUrl, t.get(org.reverseProxyPath), t.get(req.targetUrl)));
|
||||
wr.setSecret(t.get(req.secret));
|
||||
wr.setEventType(event);
|
||||
wr.setData(new ArrayList<String>());
|
||||
@@ -95,7 +97,7 @@ public class WebhookService {
|
||||
}
|
||||
|
||||
//reverse proxy 서버로 발송하기 위한 url 만든다
|
||||
private String convertTargetUrl(String proxyUrl, String path, String targetUrl) {
|
||||
private String getProxyUrl(String proxyUrl, String path, String targetUrl) {
|
||||
String base = stripTrailingSlash(proxyUrl);
|
||||
String normalizedPath = stripSlashes(path);
|
||||
// scheme://host[:port] 부분만 제거하고 이후 path/query/fragment는 그대로 유지
|
||||
@@ -141,6 +143,7 @@ public class WebhookService {
|
||||
public void send(WebhookSendRequest req) {
|
||||
|
||||
String targetUrl = req.getTargetUrl();
|
||||
String proxyUrl = req.getProxyUrl();
|
||||
String secret = req.getSecret();
|
||||
String eventType = req.getEventType();
|
||||
Object data = req.getData();
|
||||
@@ -148,7 +151,9 @@ public class WebhookService {
|
||||
WebhookSendLog sendLog = new WebhookSendLog();
|
||||
sendLog.setOrgId(req.getOrgId());
|
||||
sendLog.setTargetUrl(targetUrl);
|
||||
sendLog.setProxyUrl(proxyUrl);
|
||||
sendLog.setEventType(eventType);
|
||||
sendLog.setSendBy(ContainerUtil.getInstanceName());
|
||||
|
||||
try {
|
||||
// 1. Payload 생성
|
||||
@@ -172,7 +177,7 @@ public class WebhookService {
|
||||
int retryTime = monitoringContext.getIntProperty(MonitoringContext.RMS_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
||||
sendLog.setSentAt(LocalDateTime.now());
|
||||
HttpEntity<String> httpRequest = new HttpEntity<>(payloadJson, headers);
|
||||
ResponseEntity<String> response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||
ResponseEntity<String> response = sendWithRetry(proxyUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||
|
||||
// 5. 성공 로그
|
||||
sendLog.setStatusCode(response.getStatusCode().value());
|
||||
@@ -180,7 +185,7 @@ public class WebhookService {
|
||||
sendLog.setSuccess(response.getStatusCode().is2xxSuccessful());
|
||||
|
||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}",
|
||||
eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||
eventType, proxyUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||
|
||||
} catch (HttpClientErrorException | HttpServerErrorException e) {
|
||||
sendLog.setStatusCode(e.getStatusCode().value());
|
||||
@@ -188,13 +193,13 @@ public class WebhookService {
|
||||
sendLog.setSuccess(false);
|
||||
sendLog.setErrorMessage(e.getMessage());
|
||||
log.error("[Webhook] HTTP 오류 - eventType: {}, url: {}, status: {}",
|
||||
eventType, targetUrl, e.getStatusCode());
|
||||
eventType, proxyUrl, e.getStatusCode());
|
||||
|
||||
} catch (Exception e) {
|
||||
sendLog.setSuccess(false);
|
||||
sendLog.setErrorMessage(e.getMessage());
|
||||
log.error("[Webhook] 발송 실패 - eventType: {}, url: {}, error: {}",
|
||||
eventType, targetUrl, e.getMessage());
|
||||
eventType, proxyUrl, e.getMessage());
|
||||
}
|
||||
|
||||
sendLogRepository.save(sendLog);
|
||||
@@ -206,24 +211,25 @@ public class WebhookService {
|
||||
/**
|
||||
* 재시도 포함 HTTP 발송
|
||||
* - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요)
|
||||
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 exponential backoff 재시도 (1s → 2s → 4s)
|
||||
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 선형 증가 backoff 재시도 (1s → 2s → 3s → 4s → 5s)
|
||||
*/
|
||||
private ResponseEntity<String> sendWithRetry(String targetUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||
private ResponseEntity<String> sendWithRetry(String proxyUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||
Exception lastException = null;
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++) {
|
||||
try {
|
||||
if (attempt > 0) {
|
||||
long delayMs = retryTime * (1L << (attempt - 1));
|
||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs);
|
||||
Thread.sleep(delayMs);
|
||||
long delayMs = (long) retryTime * attempt;
|
||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, proxyUrl, delayMs);
|
||||
sendLog.setRetryCount(attempt);
|
||||
Thread.sleep(delayMs);
|
||||
}
|
||||
return restTemplate.postForEntity(targetUrl, request, String.class);
|
||||
return restTemplate.postForEntity(proxyUrl, request, String.class);
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw e; // 4xx는 재시도 불필요
|
||||
//throw e; // 4xx는 재시도 불필요
|
||||
lastException = e; //TODO: 재시도 테스트
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage());
|
||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, proxyUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
throw lastException;
|
||||
|
||||
Reference in New Issue
Block a user