From f23cb02b1ce482fed84ee5e03019497d4eb2293d Mon Sep 17 00:00:00 2001 From: eastargh Date: Fri, 3 Jul 2026 17:15:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9B=B9=ED=9B=85=20=EB=B0=9C=EC=86=A1=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onl/apim/portalorg/portalOrgManDetail.jsp | 2 +- .../onl/apim/webhook/webhookSendLogMan.jsp | 10 +++--- .../apim/webhook/webhookSendLogManDetail.jsp | 11 +++--- .../onl/djb/webhook/WebhookSendLog.java | 8 ++++- .../ext/djb/webhook/dto/WebhookSendLogUI.java | 2 ++ .../webhook/dto/WebhookSendLogUISearch.java | 2 +- .../djb/webhook/dto/WebhookSendRequest.java | 1 + .../service/WebhookSendLogManService.java | 6 ++-- .../djb/webhook/service/WebhookService.java | 34 +++++++++++-------- 9 files changed, 49 insertions(+), 27 deletions(-) diff --git a/WebContent/jsp/onl/apim/portalorg/portalOrgManDetail.jsp b/WebContent/jsp/onl/apim/portalorg/portalOrgManDetail.jsp index e65de15..9af8953 100644 --- a/WebContent/jsp/onl/apim/portalorg/portalOrgManDetail.jsp +++ b/WebContent/jsp/onl/apim/portalorg/portalOrgManDetail.jsp @@ -11,7 +11,7 @@ response.setHeader("Expires", "0"); MonitoringContext monitoringContext = (MonitoringContext)CommonUtil.getBean(request, "monitoringContext"); - String reverseProxyUrl = monitoringContext.getStringProperty("djb.reverse_proxy.url", ""); + String reverseProxyUrl = monitoringContext.getStringProperty(MonitoringContext.RMS_WEBHOOK_REVERSE_PROXY_URL, ""); %> <%! public String getRequiredLabel(String label) { diff --git a/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp b/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp index bf663fc..fba0ece 100644 --- a/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp +++ b/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp @@ -23,7 +23,7 @@ var url = ''; var url_view = ''; var combo; - + function escapeHtmlJs(s) { return $('
').text(s == null ? '' : s).html(); } function escapeAttr(s) { return escapeHtmlJs(s).replace(/"/g, '"'); } @@ -89,16 +89,17 @@ mtype: 'POST', url: url, postData: getSearchForJqgrid("cmd", "LIST"), - colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Status', '성공여부', '등록일시', '발송일시'], + colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Proxy URL', 'Status', '성공여부', '발송서버', '발송일시'], colModel: [ {name: 'id', align: 'center', key: true, hidden: true}, {name: 'rowNum', align: 'center', width: 45, sortable: false}, {name: 'orgName', align: 'left', width: 140}, {name: 'eventType', align: 'center', width: 120, formatter: formatEventType}, {name: 'targetUrl', align: 'left', width: 300, formatter: formatTargetUrl}, + {name: 'proxyUrl', align: 'left', width: 300, formatter: formatTargetUrl}, {name: 'statusCode', align: 'center', width: 60}, {name: 'success', align: 'center', width: 70, formatter: formatSuccess}, - {name: 'createdAt', align: 'center', width: 140}, + {name: 'sendBy', align: 'center', width: 100}, {name: 'sentAt', align: 'center', width: 140} ], jsonReader: {repeatitems: false}, @@ -118,6 +119,7 @@ url2 += '&returnUrl=' + getReturnUrl(); url2 += '&menuId=' + '${param.menuId}'; url2 += '&id=' + id; + url2 += '&' + getSearchUrl(); goNav(url2); }, loadComplete: function () { @@ -218,7 +220,7 @@ Target URL - 등록기간 + 발송기간 ~ diff --git a/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp b/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp index c193c00..c22b9f9 100644 --- a/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp +++ b/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp @@ -37,9 +37,10 @@ $('#eventType').text(data.eventTypeName || data.eventType || '') .attr('title', data.eventType || ''); $('#targetUrl').text(data.targetUrl || ''); + $('#proxyUrl').text(data.proxyUrl || ''); $('#statusCode').text(data.statusCode != null ? data.statusCode : ''); $('#retryCount').text(data.retryCount != null ? data.retryCount : ''); - $('#createdAt').text(data.createdAt || ''); + $('#sendBy').text(data.sendBy || ''); $('#sentAt').text(data.sentAt || ''); $('#signature').text(data.signature || ''); $('#errorMessage').text(data.errorMessage || ''); @@ -97,7 +98,9 @@ Target URL - + + Proxy URL + 성공여부 @@ -106,8 +109,8 @@ - 등록일시 - + 발송서버 + 발송일시 diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java index 5c82e6e..1900d34 100644 --- a/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java +++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/djb/webhook/WebhookSendLog.java @@ -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"; diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUI.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUI.java index 789a734..7b9afac 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUI.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUI.java @@ -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; diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUISearch.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUISearch.java index 35e263d..c7f4e4f 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUISearch.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUISearch.java @@ -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; } diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java index 729bd72..3130aff 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendRequest.java @@ -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; diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendLogManService.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendLogManService.java index fb878b2..dec6936 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendLogManService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendLogManService.java @@ -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 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)); diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java index e27bd9f..34233c1 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookService.java @@ -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()); @@ -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 httpRequest = new HttpEntity<>(payloadJson, headers); - ResponseEntity response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime); + ResponseEntity 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 sendWithRetry(String targetUrl, HttpEntity request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception { + private ResponseEntity sendWithRetry(String proxyUrl, HttpEntity 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;