@@ -730,16 +730,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th><%= localeMessage.getString("portalOrg.reverseProxyPath") %></th>
|
|
||||||
<td colspan="3">
|
|
||||||
<%=reverseProxyUrl %>/<input type="text" name="reverseProxyPath" id="reverseProxyPath" style="width:100px; "/>
|
|
||||||
</td>
|
|
||||||
<th></th>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr><%--사업자등록증--%>
|
<tr><%--사업자등록증--%>
|
||||||
<th><%= localeMessage.getString("portalOrg.compRegFile") %></th>
|
<th><%= localeMessage.getString("portalOrg.compRegFile") %></th>
|
||||||
<td colspan="5" style="padding-top: 5px; padding-bottom: 5px;">
|
<td colspan="5" style="padding-top: 5px; padding-bottom: 5px;">
|
||||||
|
|||||||
@@ -89,14 +89,13 @@
|
|||||||
mtype: 'POST',
|
mtype: 'POST',
|
||||||
url: url,
|
url: url,
|
||||||
postData: getSearchForJqgrid("cmd", "LIST"),
|
postData: getSearchForJqgrid("cmd", "LIST"),
|
||||||
colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Proxy URL', 'Status', '성공여부', '재시도', '발송서버', '발송일시'],
|
colNames: ['id', 'No.', '기관명', '이벤트유형', 'Target URL', 'Status', '성공여부', '재시도', '발송서버', '발송일시'],
|
||||||
colModel: [
|
colModel: [
|
||||||
{name: 'id', align: 'center', key: true, hidden: true},
|
{name: 'id', align: 'center', key: true, hidden: true},
|
||||||
{name: 'rowNum', align: 'center', width: 45, sortable: false},
|
{name: 'rowNum', align: 'center', width: 45, sortable: false},
|
||||||
{name: 'orgName', align: 'left', width: 140},
|
{name: 'orgName', align: 'left', width: 140},
|
||||||
{name: 'eventType', align: 'center', width: 120, formatter: formatEventType},
|
{name: 'eventType', align: 'center', width: 120, formatter: formatEventType},
|
||||||
{name: 'targetUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
{name: 'targetUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
||||||
{name: 'proxyUrl', align: 'left', width: 300, formatter: formatTargetUrl},
|
|
||||||
{name: 'statusCode', align: 'center', width: 60},
|
{name: 'statusCode', align: 'center', width: 60},
|
||||||
{name: 'success', align: 'center', width: 70, formatter: formatSuccess},
|
{name: 'success', align: 'center', width: 70, formatter: formatSuccess},
|
||||||
{name: 'retryCount', align: 'center', width: 60},
|
{name: 'retryCount', align: 'center', width: 60},
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
$('#eventType').text(data.eventTypeName || data.eventType || '')
|
$('#eventType').text(data.eventTypeName || data.eventType || '')
|
||||||
.attr('title', data.eventType || '');
|
.attr('title', data.eventType || '');
|
||||||
$('#targetUrl').text(data.targetUrl || '');
|
$('#targetUrl').text(data.targetUrl || '');
|
||||||
$('#proxyUrl').text(data.proxyUrl || '');
|
|
||||||
$('#statusCode').text(data.statusCode != null ? data.statusCode : '');
|
$('#statusCode').text(data.statusCode != null ? data.statusCode : '');
|
||||||
$('#retryCount').text(data.retryCount != null ? data.retryCount : '');
|
$('#retryCount').text(data.retryCount != null ? data.retryCount : '');
|
||||||
$('#sendBy').text(data.sendBy || '');
|
$('#sendBy').text(data.sendBy || '');
|
||||||
@@ -99,9 +98,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Target URL</th>
|
<th>Target URL</th>
|
||||||
<td><span id="targetUrl"></span></td>
|
<td colspan="3"><span id="targetUrl"></span></td>
|
||||||
<th>Proxy URL</th>
|
|
||||||
<td><span id="proxyUrl"></span></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>성공여부</th>
|
<th>성공여부</th>
|
||||||
|
|||||||
@@ -272,7 +272,6 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
- `rms.webhook.enabled` - 웹훅 발송 여부
|
- `rms.webhook.enabled` - 웹훅 발송 여부
|
||||||
- `rms.webhook.retry_count` - 웹훅 재발송 횟수
|
- `rms.webhook.retry_count` - 웹훅 재발송 횟수
|
||||||
- `rms.webhook.retry_time` - 웹훅 재발송 시간간격
|
- `rms.webhook.retry_time` - 웹훅 재발송 시간간격
|
||||||
- `rms.webhook.reverse_proxy.url` - 리버스 프록시 경로
|
|
||||||
|
|
||||||
|
|
||||||
> Portal 프라퍼티 (파트너포탈 > 포탈관리 > Property관리)
|
> Portal 프라퍼티 (파트너포탈 > 포탈관리 > Property관리)
|
||||||
@@ -296,6 +295,12 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
- `api.status.delay.range_minute` - API 지연 상태 판단을 위한 로그 구간(분)
|
- `api.status.delay.range_minute` - API 지연 상태 판단을 위한 로그 구간(분)
|
||||||
- `api.status.delay.avg_resp_time` - API 지연 상태 판단을 위한 평균 응답속도 (ex: 10분동안 평균 응답속도가 10초이상이면 지연으로 판단)
|
- `api.status.delay.avg_resp_time` - API 지연 상태 판단을 위한 평균 응답속도 (ex: 10분동안 평균 응답속도가 10초이상이면 지연으로 판단)
|
||||||
|
|
||||||
|
**API 점검 사전 공지 웹훅 발송**
|
||||||
|
- `ApiIncidentPrenoticeJob` - API 점검이 시작되기 전에 사전 공지를 웹훅으로 발송한다
|
||||||
|
- 실행간격(10분) CLUSTERED로 구성한다
|
||||||
|
- 공지사항에 점검 게시물을 등록할때 사전발송일에 일시가 등록된 경우 발송된다.
|
||||||
|
- 웹훅 신청시 `점검시작(CHECK_NOTICE)` 이벤트유형을 선택한 제휴사에게 웹훅을 발송한다
|
||||||
|
|
||||||
**유량제어토큰실패감시**
|
**유량제어토큰실패감시**
|
||||||
- `InflowTokenMonitorJob` - 유량제어 토큰 획득 실패(TSEAIFR11) 발생시 직원에게 메신저를 발송한다
|
- `InflowTokenMonitorJob` - 유량제어 토큰 획득 실패(TSEAIFR11) 발생시 직원에게 메신저를 발송한다
|
||||||
- `api.inflow.fail.range_minute` - 최근 몇분동안 감시하는지 설정
|
- `api.inflow.fail.range_minute` - 최근 몇분동안 감시하는지 설정
|
||||||
@@ -320,7 +325,7 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
**토큰발급이력삭제**
|
**토큰발급이력삭제**
|
||||||
- `TOKENISSUANCELOGCLEANUPJOB` - 7일이 지난 토큰 발급내역을 삭제한다
|
- `TOKENISSUANCELOGCLEANUPJOB` - 7일이 지난 토큰 발급내역을 삭제한다
|
||||||
|
|
||||||
## IIM 연동 - 제주은행 커스텀
|
## 레이아웃 연동 - IIM(제주은행 커스텀 버전)
|
||||||
|
|
||||||
**인터페이스**
|
**인터페이스**
|
||||||
- 최초 연동시 APIM에 등록되지 않은 경우에만 인터페이스를 생성한다.
|
- 최초 연동시 APIM에 등록되지 않은 경우에만 인터페이스를 생성한다.
|
||||||
@@ -336,9 +341,20 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
- 전문이 등록된 후 API 관리화면에서 필요한 정보를 입력하고 **수정버튼을 클릭하여 G/W에 반영된다**
|
- 전문이 등록된 후 API 관리화면에서 필요한 정보를 입력하고 **수정버튼을 클릭하여 G/W에 반영된다**
|
||||||
|
|
||||||
**연동이력**
|
**연동이력**
|
||||||
- 연동 이력은 `환경정보 > EMS관리 > 연동 배포 이력` 메뉴에서 조회한다.
|
- 연동 이력은 `환경정보 > EMS관리 > 연동 배포 이력` 메뉴에서 조회한다.(서비스명 : MessageSync)
|
||||||
|
|
||||||
## 배포
|
## 인터페이스 운영 배포
|
||||||
|
|
||||||
|
**eCams**
|
||||||
|
- 제주은행 형상관리 시스템으로 개발서버의 인터페이스를 운영서버로 배포한다
|
||||||
|
- eCams에서 실행시 아래 폴더에 json을 생성한다. (스펙정보 포함)
|
||||||
|
- `iomap.download.path` - 인터페이스.json 파일 다운로드 개발서버 경로
|
||||||
|
- `iomap.upload.path` - 인터페이스.json 파일 업로드 운영서버 경로
|
||||||
|
|
||||||
|
**배포이력**
|
||||||
|
- 배포 이력은 `환경정보 > EMS관리 > 연동 배포 이력` 메뉴에서 조회한다.(서비스명 : interfaceDeploy)
|
||||||
|
|
||||||
|
## 소스배포
|
||||||
|
|
||||||
| 대상 | 비고 |
|
| 대상 | 비고 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ public interface MonitoringContext {
|
|||||||
|
|
||||||
// 웹훅
|
// 웹훅
|
||||||
public static final String RMS_WEBHOOK_ENABLED = "rms.webhook.enabled";
|
public static final String RMS_WEBHOOK_ENABLED = "rms.webhook.enabled";
|
||||||
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_COUNT = "rms.webhook.retry_count";
|
||||||
public static final String RMS_WEBHOOK_RETRY_TIME = "rms.webhook.retry_time";
|
public static final String RMS_WEBHOOK_RETRY_TIME = "rms.webhook.retry_time";
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ public class WebhookSendLog {
|
|||||||
@Column(name = "TARGET_URL", length = 500)
|
@Column(name = "TARGET_URL", length = 500)
|
||||||
private String targetUrl;
|
private String targetUrl;
|
||||||
|
|
||||||
@Column(name = "PROXY_URL", length = 500)
|
|
||||||
private String proxyUrl;
|
|
||||||
|
|
||||||
@Column(name = "EVENT_TYPE", length = 100)
|
@Column(name = "EVENT_TYPE", length = 100)
|
||||||
private String eventType;
|
private String eventType;
|
||||||
|
|
||||||
|
|||||||
+8
-10
@@ -147,7 +147,8 @@ public class ApiIncidentPrenoticeService {
|
|||||||
List<String> failures = new ArrayList<>();
|
List<String> failures = new ArrayList<>();
|
||||||
for (WebhookSendRequest req : sendList) {
|
for (WebhookSendRequest req : sendList) {
|
||||||
req.setEventType(NOTICE_EVENT);
|
req.setEventType(NOTICE_EVENT);
|
||||||
req.setData(buildNoticeData(incident, req.getData()));
|
req.setExtra(buildNoticeExtra(incident));
|
||||||
|
// data(apiIds 배열)는 findSendList 가 이미 채워둔 값을 그대로 사용한다 - 장애/지연 웹훅과 동일한 형식
|
||||||
|
|
||||||
WebhookSendLog result = webhookService.send(req);
|
WebhookSendLog result = webhookService.send(req);
|
||||||
if (result != null && Boolean.TRUE.equals(result.getSuccess())) {
|
if (result != null && Boolean.TRUE.equals(result.getSuccess())) {
|
||||||
@@ -168,15 +169,12 @@ public class ApiIncidentPrenoticeService {
|
|||||||
incidentId, targetCnt, sentCnt, result);
|
incidentId, targetCnt, sentCnt, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 원래 org 기준 apiId 목록(req.getData())은 유지하고, 점검 예정 일정을 덧붙인다. */
|
/** 점검 예정 시작/종료일시 - payload 최상위(data와 형제)에 실린다. */
|
||||||
private Map<String, Object> buildNoticeData(DjbApistatusIncident incident, Object apiIds) {
|
private Map<String, Object> buildNoticeExtra(DjbApistatusIncident incident) {
|
||||||
Map<String, Object> data = new LinkedHashMap<>();
|
Map<String, Object> extra = new LinkedHashMap<>();
|
||||||
data.put("incidentId", incident.getIncidentId());
|
extra.put("scheduledStartAt", formatNoticeDate(incident.getStartedAt()));
|
||||||
data.put("title", incident.getTitle());
|
extra.put("scheduledEndAt", formatNoticeDate(incident.getEndAt()));
|
||||||
data.put("scheduledStartAt", formatNoticeDate(incident.getStartedAt()));
|
return extra;
|
||||||
data.put("scheduledEndAt", formatNoticeDate(incident.getEndAt()));
|
|
||||||
data.put("apiIds", apiIds);
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatNoticeDate(LocalDateTime dateTime) {
|
private String formatNoticeDate(LocalDateTime dateTime) {
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.eactive.eai.rms.ext.djb.webhook.dto;
|
package com.eactive.eai.rms.ext.djb.webhook.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -21,13 +24,26 @@ public class WebhookPayload {
|
|||||||
private long timestamp;
|
private long timestamp;
|
||||||
private Object data;
|
private Object data;
|
||||||
|
|
||||||
|
/** 이벤트별 추가 정보(예: 점검 예정 시작/종료일시) - JSON 최상위(data와 형제)로 펼쳐진다. */
|
||||||
|
private Map<String, Object> extra;
|
||||||
|
|
||||||
|
@JsonAnyGetter
|
||||||
|
public Map<String, Object> getExtra() {
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
|
||||||
public static WebhookPayload of(String eventType, String message, Object data) {
|
public static WebhookPayload of(String eventType, String message, Object data) {
|
||||||
|
return of(eventType, message, data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebhookPayload of(String eventType, String message, Object data, Map<String, Object> extra) {
|
||||||
return WebhookPayload.builder()
|
return WebhookPayload.builder()
|
||||||
.eventType(eventType)
|
.eventType(eventType)
|
||||||
.eventId(UUID.randomUUID().toString())
|
.eventId(UUID.randomUUID().toString())
|
||||||
.message(message)
|
.message(message)
|
||||||
.timestamp(System.currentTimeMillis())
|
.timestamp(System.currentTimeMillis())
|
||||||
.data(data)
|
.data(data)
|
||||||
|
.extra(extra)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ public class WebhookSendLogUI {
|
|||||||
/** 모니터링 공통코드(EVENT_TYPE) 코드명. 상세 조회에서만 채워진다(목록은 화면에서 콤보로 변환). */
|
/** 모니터링 공통코드(EVENT_TYPE) 코드명. 상세 조회에서만 채워진다(목록은 화면에서 콤보로 변환). */
|
||||||
private String eventTypeName;
|
private String eventTypeName;
|
||||||
private String targetUrl;
|
private String targetUrl;
|
||||||
private String proxyUrl;
|
|
||||||
|
|
||||||
private String payload;
|
private String payload;
|
||||||
private String signature;
|
private String signature;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.eactive.eai.rms.ext.djb.webhook.dto;
|
package com.eactive.eai.rms.ext.djb.webhook.dto;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -8,10 +10,10 @@ import lombok.Setter;
|
|||||||
public class WebhookSendRequest {
|
public class WebhookSendRequest {
|
||||||
private String orgId;
|
private String orgId;
|
||||||
private String targetUrl;
|
private String targetUrl;
|
||||||
private String proxyUrl;
|
|
||||||
private String eventType;
|
private String eventType;
|
||||||
private String secret;
|
private String secret;
|
||||||
private String reverseProxyPath;
|
|
||||||
private String userSecret;
|
private String userSecret;
|
||||||
private Object data;
|
private Object data;
|
||||||
|
/** 이벤트별 웹훅 payload 최상위에 실을 추가 정보 (예: CHECK_NOTICE의 점검 예정 시작/종료일시) */
|
||||||
|
private Map<String, Object> extra;
|
||||||
}
|
}
|
||||||
-1
@@ -154,7 +154,6 @@ public class WebhookSendLogManService extends BaseService {
|
|||||||
ui.setEventType(e.getEventType());
|
ui.setEventType(e.getEventType());
|
||||||
ui.setEventTypeName(eventTypeName);
|
ui.setEventTypeName(eventTypeName);
|
||||||
ui.setTargetUrl(e.getTargetUrl());
|
ui.setTargetUrl(e.getTargetUrl());
|
||||||
ui.setProxyUrl(e.getProxyUrl());
|
|
||||||
ui.setPayload(e.getPayload());
|
ui.setPayload(e.getPayload());
|
||||||
ui.setSignature(e.getSignature());
|
ui.setSignature(e.getSignature());
|
||||||
ui.setUserSecret(e.getUserSecret());
|
ui.setUserSecret(e.getUserSecret());
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import org.springframework.web.client.HttpClientErrorException;
|
|||||||
import org.springframework.web.client.HttpServerErrorException;
|
import org.springframework.web.client.HttpServerErrorException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
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.context.MonitoringContext;
|
||||||
import com.eactive.eai.rms.common.util.ContainerUtil;
|
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.QWebhookReq;
|
||||||
@@ -61,16 +60,12 @@ public class WebhookService {
|
|||||||
QWebhookReq req = QWebhookReq.webhookReq;
|
QWebhookReq req = QWebhookReq.webhookReq;
|
||||||
QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent;
|
QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent;
|
||||||
QWebhookReqApi api = QWebhookReqApi.webhookReqApi;
|
QWebhookReqApi api = QWebhookReqApi.webhookReqApi;
|
||||||
QPortalOrg org = QPortalOrg.portalOrg;
|
|
||||||
|
|
||||||
String reverseProxyUrl = monitoringContext.getProperty(MonitoringContext.RMS_WEBHOOK_REVERSE_PROXY_URL);
|
|
||||||
|
|
||||||
List<Tuple> tuples = new JPAQueryFactory(entityManager)
|
List<Tuple> tuples = new JPAQueryFactory(entityManager)
|
||||||
.select(req.orgId, req.targetUrl, req.secret, req.userSecret, org.reverseProxyPath, api.id.apiId)
|
.select(req.orgId, req.targetUrl, req.secret, req.userSecret, api.id.apiId)
|
||||||
.from(req)
|
.from(req)
|
||||||
.join(evt).on(req.id.eq(evt.id.webhookReqId))
|
.join(evt).on(req.id.eq(evt.id.webhookReqId))
|
||||||
.join(api).on(req.id.eq(api.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)
|
.where(evt.id.eventType.eq(event)
|
||||||
.and(api.id.apiId.in(apiIds)))
|
.and(api.id.apiId.in(apiIds)))
|
||||||
.fetch();
|
.fetch();
|
||||||
@@ -83,14 +78,13 @@ public class WebhookService {
|
|||||||
WebhookSendRequest wr = new WebhookSendRequest();
|
WebhookSendRequest wr = new WebhookSendRequest();
|
||||||
wr.setOrgId(t.get(req.orgId));
|
wr.setOrgId(t.get(req.orgId));
|
||||||
wr.setTargetUrl(t.get(req.targetUrl));
|
wr.setTargetUrl(t.get(req.targetUrl));
|
||||||
wr.setProxyUrl(this.getProxyUrl(reverseProxyUrl, t.get(org.reverseProxyPath)));
|
|
||||||
wr.setSecret(t.get(req.secret));
|
wr.setSecret(t.get(req.secret));
|
||||||
wr.setUserSecret(t.get(req.userSecret));
|
wr.setUserSecret(t.get(req.userSecret));
|
||||||
wr.setEventType(event);
|
wr.setEventType(event);
|
||||||
wr.setData(new ArrayList<String>());
|
wr.setData(new ArrayList<String>());
|
||||||
return wr;
|
return wr;
|
||||||
});
|
});
|
||||||
|
|
||||||
WebhookSendRequest wsr = resultMap.get(key);
|
WebhookSendRequest wsr = resultMap.get(key);
|
||||||
if (wsr != null) {
|
if (wsr != null) {
|
||||||
List<String> list = (List<String>) wsr.getData();
|
List<String> list = (List<String>) wsr.getData();
|
||||||
@@ -102,46 +96,6 @@ public class WebhookService {
|
|||||||
|
|
||||||
return new ArrayList<>(resultMap.values());
|
return new ArrayList<>(resultMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
//reverse proxy 서버로 발송하기 위한 url 만든다
|
|
||||||
private String getProxyUrl(String proxyUrl, String path) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 웹훅 발송 메인 메서드
|
* 웹훅 발송 메인 메서드
|
||||||
@@ -156,24 +110,23 @@ public class WebhookService {
|
|||||||
public WebhookSendLog send(WebhookSendRequest req) {
|
public WebhookSendLog send(WebhookSendRequest req) {
|
||||||
|
|
||||||
String targetUrl = req.getTargetUrl();
|
String targetUrl = req.getTargetUrl();
|
||||||
String proxyUrl = req.getProxyUrl();
|
|
||||||
String secret = req.getSecret();
|
String secret = req.getSecret();
|
||||||
String userSecret = req.getUserSecret();
|
String userSecret = req.getUserSecret();
|
||||||
String eventType = req.getEventType();
|
String eventType = req.getEventType();
|
||||||
String message = getMessage(eventType);
|
String message = getMessage(eventType);
|
||||||
Object data = req.getData();
|
Object data = req.getData();
|
||||||
|
Map<String, Object> extra = req.getExtra();
|
||||||
|
|
||||||
WebhookSendLog sendLog = new WebhookSendLog();
|
WebhookSendLog sendLog = new WebhookSendLog();
|
||||||
sendLog.setOrgId(req.getOrgId());
|
sendLog.setOrgId(req.getOrgId());
|
||||||
sendLog.setTargetUrl(targetUrl);
|
sendLog.setTargetUrl(targetUrl);
|
||||||
sendLog.setProxyUrl(proxyUrl);
|
|
||||||
sendLog.setEventType(eventType);
|
sendLog.setEventType(eventType);
|
||||||
sendLog.setUserSecret(userSecret);
|
sendLog.setUserSecret(userSecret);
|
||||||
sendLog.setSendBy(ContainerUtil.getInstanceName());
|
sendLog.setSendBy(ContainerUtil.getInstanceName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Payload 생성
|
// 1. Payload 생성
|
||||||
WebhookPayload webhookPayload = WebhookPayload.of(eventType, message, data);
|
WebhookPayload webhookPayload = WebhookPayload.of(eventType, message, data, extra);
|
||||||
String payloadJson = objectMapper.writeValueAsString(webhookPayload);
|
String payloadJson = objectMapper.writeValueAsString(webhookPayload);
|
||||||
sendLog.setPayload(payloadJson);
|
sendLog.setPayload(payloadJson);
|
||||||
|
|
||||||
@@ -194,7 +147,7 @@ public class WebhookService {
|
|||||||
int retryTime = monitoringContext.getIntProperty(MonitoringContext.RMS_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
int retryTime = monitoringContext.getIntProperty(MonitoringContext.RMS_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
||||||
sendLog.setSentAt(LocalDateTime.now());
|
sendLog.setSentAt(LocalDateTime.now());
|
||||||
HttpEntity<String> httpRequest = new HttpEntity<>(payloadJson, headers);
|
HttpEntity<String> httpRequest = new HttpEntity<>(payloadJson, headers);
|
||||||
ResponseEntity<String> response = sendWithRetry(proxyUrl, httpRequest, sendLog, retryCount, retryTime);
|
ResponseEntity<String> response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||||
|
|
||||||
// 5. 성공 로그
|
// 5. 성공 로그
|
||||||
sendLog.setStatusCode(response.getStatusCode().value());
|
sendLog.setStatusCode(response.getStatusCode().value());
|
||||||
@@ -202,7 +155,7 @@ public class WebhookService {
|
|||||||
sendLog.setSuccess(response.getStatusCode().is2xxSuccessful());
|
sendLog.setSuccess(response.getStatusCode().is2xxSuccessful());
|
||||||
|
|
||||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}",
|
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}",
|
||||||
eventType, proxyUrl, response.getStatusCode(), sendLog.getRetryCount());
|
eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||||
|
|
||||||
} catch (HttpClientErrorException | HttpServerErrorException e) {
|
} catch (HttpClientErrorException | HttpServerErrorException e) {
|
||||||
sendLog.setStatusCode(e.getStatusCode().value());
|
sendLog.setStatusCode(e.getStatusCode().value());
|
||||||
@@ -210,13 +163,13 @@ public class WebhookService {
|
|||||||
sendLog.setSuccess(false);
|
sendLog.setSuccess(false);
|
||||||
sendLog.setErrorMessage(e.getMessage());
|
sendLog.setErrorMessage(e.getMessage());
|
||||||
log.error("[Webhook] HTTP 오류 - eventType: {}, url: {}, status: {}",
|
log.error("[Webhook] HTTP 오류 - eventType: {}, url: {}, status: {}",
|
||||||
eventType, proxyUrl, e.getStatusCode());
|
eventType, targetUrl, e.getStatusCode());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
sendLog.setSuccess(false);
|
sendLog.setSuccess(false);
|
||||||
sendLog.setErrorMessage(e.getMessage());
|
sendLog.setErrorMessage(e.getMessage());
|
||||||
log.error("[Webhook] 발송 실패 - eventType: {}, url: {}, error: {}",
|
log.error("[Webhook] 발송 실패 - eventType: {}, url: {}, error: {}",
|
||||||
eventType, proxyUrl, e.getMessage());
|
eventType, targetUrl, e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendLogRepository.save(sendLog);
|
return sendLogRepository.save(sendLog);
|
||||||
@@ -243,29 +196,29 @@ public class WebhookService {
|
|||||||
* - 실패할 때마다 즉시 로그를 저장한다 (최초 실패는 INSERT, 이후 재시도 실패는 같은 row를 UPDATE) -
|
* - 실패할 때마다 즉시 로그를 저장한다 (최초 실패는 INSERT, 이후 재시도 실패는 같은 row를 UPDATE) -
|
||||||
* 재시도 도중 앱이 죽어도 마지막으로 저장된 실패 기록이 남도록 하기 위함
|
* 재시도 도중 앱이 죽어도 마지막으로 저장된 실패 기록이 남도록 하기 위함
|
||||||
*/
|
*/
|
||||||
private ResponseEntity<String> sendWithRetry(String proxyUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
private ResponseEntity<String> sendWithRetry(String targetUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||||
Exception lastException = null;
|
Exception lastException = null;
|
||||||
for (int attempt = 0; attempt <= retryCount; attempt++) {
|
for (int attempt = 0; attempt <= retryCount; attempt++) {
|
||||||
try {
|
try {
|
||||||
if (attempt == 0) {
|
if (attempt == 0) {
|
||||||
// 배포 반영 확인용: HttpComponentsClientHttpRequestFactory 여야 SSL 우회 RestTemplate 사용 중.
|
// 배포 반영 확인용: HttpComponentsClientHttpRequestFactory 여야 SSL 우회 RestTemplate 사용 중.
|
||||||
// SimpleClientHttpRequestFactory 로 찍히면 기본 new RestTemplate() 이 주입된 것 → PKIX 원인.
|
// SimpleClientHttpRequestFactory 로 찍히면 기본 new RestTemplate() 이 주입된 것 → PKIX 원인.
|
||||||
log.info("[Webhook] POST 시도 - url: {}, requestFactory: {}", proxyUrl,
|
log.info("[Webhook] POST 시도 - url: {}, requestFactory: {}", targetUrl,
|
||||||
restTemplate.getRequestFactory().getClass().getSimpleName());
|
restTemplate.getRequestFactory().getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
if (attempt > 0) {
|
if (attempt > 0) {
|
||||||
long delayMs = (long) retryTime * attempt;
|
long delayMs = (long) retryTime * attempt;
|
||||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, proxyUrl, delayMs);
|
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs);
|
||||||
Thread.sleep(delayMs);
|
Thread.sleep(delayMs);
|
||||||
}
|
}
|
||||||
return restTemplate.postForEntity(proxyUrl, request, String.class);
|
return restTemplate.postForEntity(targetUrl, request, String.class);
|
||||||
} catch (HttpClientErrorException e) {
|
} catch (HttpClientErrorException e) {
|
||||||
//throw e; // 4xx는 재시도 불필요
|
//throw e; // 4xx는 재시도 불필요
|
||||||
lastException = e; //TODO: 재시도 테스트
|
lastException = e; //TODO: 재시도 테스트
|
||||||
recordAttemptFailure(sendLog, attempt, e.getStatusCode().value(), e.getResponseBodyAsString(), e.getMessage());
|
recordAttemptFailure(sendLog, attempt, e.getStatusCode().value(), e.getResponseBodyAsString(), e.getMessage());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
lastException = e;
|
lastException = e;
|
||||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, proxyUrl, e.getMessage());
|
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage());
|
||||||
recordAttemptFailure(sendLog, attempt, null, null, e.getMessage());
|
recordAttemptFailure(sendLog, attempt, null, null, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user