From 7b12ef2fb5196992c5b3854eaca2d897a764b45a Mon Sep 17 00:00:00 2001 From: eastargh Date: Thu, 2 Jul 2026 14:46:02 +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=20=EC=8B=A0=EA=B7=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onl/apim/webhook/webhookSendLogMan.jsp | 240 ++++++++++++++++++ .../apim/webhook/webhookSendLogManDetail.jsp | 134 ++++++++++ .../rms/common/context/MonitoringContext.java | 9 +- .../WebhookSendLogManController.java | 65 +++++ .../ext/djb/webhook/dto/WebhookSendLogUI.java | 37 +++ .../webhook/dto/WebhookSendLogUISearch.java | 23 ++ .../djb/webhook/dto/WebhookSendRequest.java | 1 + .../repository/WebhookSendLogRepository.java | 4 +- .../service/WebhookSendLogManService.java | 172 +++++++++++++ .../djb/webhook/service/WebhookService.java | 55 +++- 10 files changed, 730 insertions(+), 10 deletions(-) create mode 100644 WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp create mode 100644 WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendLogManController.java create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUI.java create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUISearch.java create mode 100644 src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendLogManService.java diff --git a/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp b/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp new file mode 100644 index 0000000..bf663fc --- /dev/null +++ b/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp @@ -0,0 +1,240 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" %> +<%@ page import="java.io.*" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> +<%@ include file="/jsp/common/include/localemessage.jsp" %> +<% + response.setHeader("Pragma", "No-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Expires", "0"); +%> + + + + + + + + + + +
+ +
+
+ +
+
웹훅 발송 로그외부로 발송된 웹훅 이력을 조회합니다.
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
기관명이벤트유형 +
+ +
+
성공여부 +
+ +
+
Target URL등록기간 + + ~ + + + +
+
+
+
+
+
+ + diff --git a/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp b/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp new file mode 100644 index 0000000..c193c00 --- /dev/null +++ b/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp @@ -0,0 +1,134 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" %> +<%@ page import="java.io.*" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> +<%@ include file="/jsp/common/include/localemessage.jsp" %> +<% + response.setHeader("Pragma", "No-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Expires", "0"); +%> + + + + + + + + + + +
+ +
+
+ +
+
웹훅 발송 로그 상세
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID기관명
이벤트유형Status Code
Target URL
성공여부재시도횟수
등록일시발송일시
Signature
Payload
Response Body
에러 메세지
+
+
+ + diff --git a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java index 530933b..598f557 100644 --- a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java +++ b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContext.java @@ -258,14 +258,17 @@ public interface MonitoringContext { // 자동 로그아웃 타임아웃 (단위: 분, 기본값: 10) public static final String RMS_AUTO_LOGOUT_TIMEOUT = "rms.auto.logout.timeout"; - // 웹훅 재전송 설정 - public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count"; - public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time"; + // 웹훅 + public static final String RMS_WEBHOOK_REVERSE_PROXY_URL = "rms.webhook.reverse_proxy.url"; + public static final String RMS_WEBHOOK_RETRY_COUNT = "rms.webhook.retry_count"; + public static final String RMS_WEBHOOK_RETRY_TIME = "rms.webhook.retry_time"; //비밀번호 초기화 접미사 public static final String RMS_PASSWORD_INIT_SUBFIX = "rms.password.init.subfix"; + + public abstract String getStringProperty(String propertyName); diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendLogManController.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendLogManController.java new file mode 100644 index 0000000..2a9efac --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/controller/WebhookSendLogManController.java @@ -0,0 +1,65 @@ +package com.eactive.eai.rms.ext.djb.webhook.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import com.eactive.eai.rms.common.base.BaseAnnotationController; +import com.eactive.eai.rms.common.combo.ComboService; +import com.eactive.eai.rms.common.vo.GridResponse; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUI; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUISearch; +import com.eactive.eai.rms.ext.djb.webhook.service.WebhookSendLogManService; + +import lombok.RequiredArgsConstructor; + +/** + * 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 조회 화면 컨트롤러. + * + *

조회 전용 (등록/수정/삭제 없음)

+ */ +@Controller +@RequiredArgsConstructor +public class WebhookSendLogManController extends BaseAnnotationController { + + private final WebhookSendLogManService webhookSendLogManService; + private final ComboService comboService; + + @GetMapping(value = "/onl/apim/webhook/webhookSendLogMan.view") + public void view() { + // 목록 view + } + + @GetMapping(value = "/onl/apim/webhook/webhookSendLogMan.view", params = "cmd=DETAIL") + public String detailView() { + return "/onl/apim/webhook/webhookSendLogManDetail"; + } + + @PostMapping(value = "/onl/apim/webhook/webhookSendLogMan.json", params = "cmd=LIST") + public ResponseEntity> selectList( + @SortDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, + WebhookSendLogUISearch search) { + Page page = webhookSendLogManService.selectList(pageable, search); + return ResponseEntity.ok(new GridResponse<>(page)); + } + + @PostMapping(value = "/onl/apim/webhook/webhookSendLogMan.json", params = "cmd=LIST_INIT_COMBO") + public ResponseEntity> initCombo() { + Map resultMap = new HashMap<>(); + resultMap.put("eventTypeList", comboService.getMonitoringCodeSortedBySeq(WebhookSendLogManService.CODE_GROUP_EVENT_TYPE)); + return ResponseEntity.ok(resultMap); + } + + @PostMapping(value = "/onl/apim/webhook/webhookSendLogMan.json", params = "cmd=DETAIL") + public ResponseEntity selectDetail(Long id) { + return ResponseEntity.ok(webhookSendLogManService.selectDetail(id)); + } +} 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 new file mode 100644 index 0000000..789a734 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUI.java @@ -0,0 +1,37 @@ +package com.eactive.eai.rms.ext.djb.webhook.dto; + +import lombok.Data; + +/** + * 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 화면 응답 DTO. + * + *

목록에서는 일부 항목만 사용하고, 상세에서는 전체 항목을 사용한다. + * 날짜는 yyyyMMdd HH:mm:ss 형식 문자열로 제공한다.

+ */ +@Data +public class WebhookSendLogUI { + + private Long id; + + private String orgId; + private String orgName; + + private String eventType; + /** 모니터링 공통코드(EVENT_TYPE) 코드명. 상세 조회에서만 채워진다(목록은 화면에서 콤보로 변환). */ + private String eventTypeName; + private String targetUrl; + + private String payload; + private String signature; + + private Integer statusCode; + private String responseBody; + + private Boolean success; + private String errorMessage; + private Integer retryCount; + + /* 등록/발송 시간 — yyyyMMdd HH:mm:ss */ + private String createdAt; + private String sentAt; +} 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 new file mode 100644 index 0000000..35e263d --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/dto/WebhookSendLogUISearch.java @@ -0,0 +1,23 @@ +package com.eactive.eai.rms.ext.djb.webhook.dto; + +import lombok.Data; + +@Data +public class WebhookSendLogUISearch { + + /* 기관명 (ptl_org.org_name, LIKE 검색) */ + private String searchOrgName; + + /* 이벤트 유형 (모니터링 공통코드 EVENT_TYPE, 콤보 선택 값과 정확히 일치) */ + private String searchEventType; + + /* 수신 URL (LIKE 검색) */ + private String searchTargetUrl; + + /* 성공여부 (Y/N, 공백=전체) */ + private String searchSuccess; + + /* 등록일시 기간 검색 (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 264338e..729bd72 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 @@ -10,5 +10,6 @@ public class WebhookSendRequest { private String targetUrl; private String eventType; private String secret; + private String reverseProxyPath; private Object data; } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java index 2233a9c..9780709 100644 --- a/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/repository/WebhookSendLogRepository.java @@ -4,17 +4,17 @@ package com.eactive.eai.rms.ext.djb.webhook.repository; import java.time.LocalDateTime; import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import com.eactive.eai.data.jpa.BaseRepository; import com.eactive.eai.rms.data.EMSDataSource; import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; @Repository @EMSDataSource -public interface WebhookSendLogRepository extends JpaRepository { +public interface WebhookSendLogRepository extends BaseRepository { // 오라클 CHAR(1) Y/N 기준 조회 @Query("SELECT l FROM WebhookSendLog l " + 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 new file mode 100644 index 0000000..fb878b2 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/ext/djb/webhook/service/WebhookSendLogManService.java @@ -0,0 +1,172 @@ +package com.eactive.eai.rms.ext.djb.webhook.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.eactive.apim.portal.monitoringcode.entity.MonitoringCode; +import com.eactive.apim.portal.monitoringcode.entity.MonitoringCodeId; +import com.eactive.apim.portal.portalorg.entity.QPortalOrg; +import com.eactive.eai.rms.common.base.BaseService; +import com.eactive.eai.rms.common.util.StringUtils; +import com.eactive.eai.rms.data.entity.man.monitoringCode.repository.MonitoringCodeRepository; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookSendLog; +import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUI; +import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendLogUISearch; +import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository; +import com.eactive.eai.rms.onl.common.exception.BizException; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +/** + * 웹훅 발송 로그(PTL_WEBHOOK_SEND_LOG) 조회 화면 서비스. + * + *

조회 전용. orgId 는 엔티티 연관관계가 아닌 단순 컬럼이므로, + * 기관명은 QueryDSL 로 ptl_org 를 별도 조회하여 매핑한다.

+ */ +@Service +@RequiredArgsConstructor +@Transactional(transactionManager = "transactionManagerForEMS") +public class WebhookSendLogManService extends BaseService { + + /** 모니터링 공통코드(tseairm28) 상의 웹훅 이벤트유형 코드 그룹. */ + public static final String CODE_GROUP_EVENT_TYPE = "EVENT_TYPE"; + + private static final DateTimeFormatter FMT_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter FMT_YYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private final WebhookSendLogRepository webhookSendLogRepository; + private final MonitoringCodeRepository monitoringCodeRepository; + + @PersistenceContext(unitName = "entityManagerFactoryForEMS") + private EntityManager entityManager; + + public Page selectList(Pageable pageable, WebhookSendLogUISearch search) { + QWebhookSendLog log = QWebhookSendLog.webhookSendLog; + BooleanBuilder predicate = new BooleanBuilder(); + + if (StringUtils.isNotBlank(search.getSearchOrgName())) { + List orgIds = findOrgIdsByName(search.getSearchOrgName()); + if (orgIds.isEmpty()) { + // 일치하는 기관이 없으면 쿼리를 실행할 필요 없이 빈 결과를 바로 반환한다. + // (QueryDSL의 where false 는 이 프로젝트의 Hibernate HQL 파서에서 지원하지 않음) + return Page.empty(pageable); + } + predicate.and(log.orgId.in(orgIds)); + } + if (StringUtils.isNotBlank(search.getSearchEventType())) { + predicate.and(log.eventType.eq(search.getSearchEventType())); + } + if (StringUtils.isNotBlank(search.getSearchTargetUrl())) { + predicate.and(log.targetUrl.containsIgnoreCase(search.getSearchTargetUrl())); + } + if (StringUtils.isNotBlank(search.getSearchSuccess())) { + predicate.and(log.success.eq(search.getSearchSuccess())); + } + if (StringUtils.isNotBlank(search.getSearchStartDate())) { + predicate.and(log.createdAt.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay())); + } + if (StringUtils.isNotBlank(search.getSearchEndDate())) { + predicate.and(log.createdAt.loe(parseYYYYMMDD(search.getSearchEndDate()).atTime(23, 59, 59))); + } + + Page page = webhookSendLogRepository.findAll(predicate, pageable); + + Map orgNameMap = findOrgNames(page.getContent()); + // 목록의 이벤트유형 코드명은 화면에서 콤보(LIST_INIT_COMBO) 기준으로 변환하므로 서버에서는 조회하지 않는다(N+1 방지). + return page.map(e -> convertToUI(e, orgNameMap.get(e.getOrgId()), null)); + } + + public WebhookSendLogUI selectDetail(Long id) { + WebhookSendLog entity = webhookSendLogRepository.findById(id) + .orElseThrow(() -> new BizException("존재하지 않는 웹훅 발송 로그입니다.")); + + Map orgNameMap = findOrgNames(Collections.singletonList(entity)); + String eventTypeName = resolveEventTypeName(entity.getEventType()); + return convertToUI(entity, orgNameMap.get(entity.getOrgId()), eventTypeName); + } + + private static LocalDate parseYYYYMMDD(String yyyyMMdd) { + return LocalDate.parse(yyyyMMdd, FMT_YYYYMMDD); + } + + /** 모니터링 공통코드(EVENT_TYPE)에서 코드명을 조회한다. 코드가 없으면 원본 코드를 그대로 반환한다. */ + private String resolveEventTypeName(String eventType) { + if (StringUtils.isBlank(eventType)) { + return eventType; + } + return monitoringCodeRepository.findById(new MonitoringCodeId(CODE_GROUP_EVENT_TYPE, eventType)) + .map(MonitoringCode::getCodeName) + .orElse(eventType); + } + + /** 기관명 LIKE 검색으로 매칭되는 orgId 목록. */ + private List findOrgIdsByName(String orgName) { + QPortalOrg org = QPortalOrg.portalOrg; + return new JPAQueryFactory(entityManager) + .select(org.id) + .from(org) + .where(org.orgName.containsIgnoreCase(orgName)) + .fetch(); + } + + /** 목록/상세에 필요한 orgId → orgName 매핑 (일괄 조회로 N+1 방지). */ + private Map findOrgNames(List logs) { + Set orgIds = logs.stream() + .map(WebhookSendLog::getOrgId) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + if (orgIds.isEmpty()) { + return Collections.emptyMap(); + } + + QPortalOrg org = QPortalOrg.portalOrg; + return new JPAQueryFactory(entityManager) + .select(org.id, org.orgName) + .from(org) + .where(org.id.in(orgIds)) + .fetch() + .stream() + .collect(Collectors.toMap(t -> t.get(org.id), t -> t.get(org.orgName))); + } + + private WebhookSendLogUI convertToUI(WebhookSendLog e, String orgName, String eventTypeName) { + WebhookSendLogUI ui = new WebhookSendLogUI(); + ui.setId(e.getId()); + ui.setOrgId(e.getOrgId()); + ui.setOrgName(orgName); + ui.setEventType(e.getEventType()); + ui.setEventTypeName(eventTypeName); + ui.setTargetUrl(e.getTargetUrl()); + ui.setPayload(e.getPayload()); + ui.setSignature(e.getSignature()); + ui.setStatusCode(e.getStatusCode()); + ui.setResponseBody(e.getResponseBody()); + ui.setSuccess(e.getSuccess()); + ui.setErrorMessage(e.getErrorMessage()); + ui.setRetryCount(e.getRetryCount()); + ui.setCreatedAt(fmt(e.getCreatedAt(), FMT_DATETIME)); + ui.setSentAt(fmt(e.getSentAt(), FMT_DATETIME)); + return ui; + } + + private static String fmt(LocalDateTime t, DateTimeFormatter f) { + return t == null ? "" : t.format(f); + } +} 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 6a2ffc1..e27bd9f 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 @@ -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.apim.portal.portalorg.entity.QPortalOrg; 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; @@ -60,24 +61,28 @@ public class WebhookService { QWebhookReq req = QWebhookReq.webhookReq; QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent; QWebhookReqApi api = QWebhookReqApi.webhookReqApi; + QPortalOrg org = QPortalOrg.portalOrg; + + String reverseProxyUrl = monitoringContext.getProperty(MonitoringContext.RMS_WEBHOOK_REVERSE_PROXY_URL); List tuples = new JPAQueryFactory(entityManager) - .select(req.orgId, req.targetUrl, req.secret, api.id.apiId) + .select(req.orgId, req.targetUrl, req.secret, org.reverseProxyPath, api.id.apiId) .from(req) .join(evt).on(req.id.eq(evt.id.webhookReqId)) .join(api).on(req.id.eq(api.id.webhookReqId)) + .join(org).on(req.orgId.eq(org.id)) .where(evt.id.eventType.eq(event) .and(api.id.apiId.in(apiIds))) .fetch(); - // orgId + targetUrl + secret 기준으로 그룹핑, apiId를 data(List)로 집계 + // orgId + targetUrl + secret 기준으로 그룹핑, apiIds를 data(List)로 집계 Map resultMap = new LinkedHashMap<>(); for (Tuple t : tuples) { String key = t.get(req.orgId) + "|" + t.get(req.targetUrl); resultMap.computeIfAbsent(key, k -> { WebhookSendRequest wr = new WebhookSendRequest(); wr.setOrgId(t.get(req.orgId)); - wr.setTargetUrl(t.get(req.targetUrl)); + wr.setTargetUrl(this.convertTargetUrl(reverseProxyUrl, t.get(org.reverseProxyPath), t.get(req.targetUrl))); wr.setSecret(t.get(req.secret)); wr.setEventType(event); wr.setData(new ArrayList()); @@ -88,6 +93,46 @@ public class WebhookService { return new ArrayList<>(resultMap.values()); } + + //reverse proxy 서버로 발송하기 위한 url 만든다 + private String convertTargetUrl(String proxyUrl, String path, String targetUrl) { + String base = stripTrailingSlash(proxyUrl); + String normalizedPath = stripSlashes(path); + // scheme://host[:port] 부분만 제거하고 이후 path/query/fragment는 그대로 유지 + String targetPath = targetUrl.replaceFirst("^[a-zA-Z][a-zA-Z0-9+.-]*://[^/]+", ""); + + StringBuilder url = new StringBuilder(base); + if (!normalizedPath.isEmpty()) { + url.append('/').append(normalizedPath); + } + url.append(targetPath); + return url.toString(); + } + + private String stripTrailingSlash(String value) { + if (value == null) { + return ""; + } + int end = value.length(); + while (end > 0 && value.charAt(end - 1) == '/') { + end--; + } + return value.substring(0, end); + } + + private String stripSlashes(String value) { + if (value == null) { + return ""; + } + String result = value.trim(); + while (result.startsWith("/")) { + result = result.substring(1); + } + while (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + return result; + } /** * 웹훅 발송 메인 메서드 @@ -123,8 +168,8 @@ public class WebhookService { headers.set("X-Webhook-Timestamp", String.valueOf(webhookPayload.getTimestamp())); // 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); + int retryCount = monitoringContext.getIntProperty(MonitoringContext.RMS_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT); + 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);