API상태변화시 스윙알림에 에러메세지, 로그조회링크 추가 & 링크 클릭시 APIM 로그인페이지에서 로그인하면 로그조회 페이지로
eapim-admin CI / build (push) Waiting to run
eapim-admin CI / build (push) Waiting to run
바로 이동
This commit is contained in:
@@ -20,6 +20,11 @@
|
|||||||
var url_view = '<c:url value="/onl/transaction/tracking/trackingMan.view" />';
|
var url_view = '<c:url value="/onl/transaction/tracking/trackingMan.view" />';
|
||||||
var url_excel = '<c:url value="/onl/transaction/tracking/trackingMan.excel" />';
|
var url_excel = '<c:url value="/onl/transaction/tracking/trackingMan.excel" />';
|
||||||
var GUID_LENGTH = <%=DbTrackingService.GUID_LENGTH%>;
|
var GUID_LENGTH = <%=DbTrackingService.GUID_LENGTH%>;
|
||||||
|
// 로그인 딥링크(세션) 또는 화면 URL에 검색조건 파라미터가 직접 붙어 들어온 경우, 최초 진입시 자동조회
|
||||||
|
// 세션 딥링크는 파라미터명 그대로 모델에 등록되어 있으므로(예: ${searchStartYYYYMMDD}) bare 변수로 확인한다.
|
||||||
|
var isDeepLinkSearch = ${not empty searchStartYYYYMMDD
|
||||||
|
or not empty param.searchStartYYYYMMDD
|
||||||
|
or not empty param.searchEndYYYYMMDD};
|
||||||
var isProdServer = <%=com.eactive.eai.common.util.SystemUtil.isProdServer()%>;
|
var isProdServer = <%=com.eactive.eai.common.util.SystemUtil.isProdServer()%>;
|
||||||
var roleString = "<%=SessionManager.getRoleIdString(request)%>";
|
var roleString = "<%=SessionManager.getRoleIdString(request)%>";
|
||||||
let totalLogs = 0;
|
let totalLogs = 0;
|
||||||
@@ -441,6 +446,10 @@
|
|||||||
getTransactionCounts();
|
getTransactionCounts();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isDeepLinkSearch) {
|
||||||
|
$("#btn_search").click();
|
||||||
|
}
|
||||||
|
|
||||||
$("input[name^=search]").keydown(function (key) {
|
$("input[name^=search]").keydown(function (key) {
|
||||||
if (key.keyCode == 13) {
|
if (key.keyCode == 13) {
|
||||||
$("#btn_search").click();
|
$("#btn_search").click();
|
||||||
@@ -568,7 +577,7 @@
|
|||||||
</div><!-- end content_top -->
|
</div><!-- end content_top -->
|
||||||
<div class="content_middle" id="content_middle">
|
<div class="content_middle" id="content_middle">
|
||||||
<div class="search_wrap">
|
<div class="search_wrap">
|
||||||
<input type="checkbox" id="schedulerToggle">
|
<input type="checkbox" id="schedulerToggle"/> <b><label for="schedulerToggle">Auto Refresh</label></b>
|
||||||
<button type="button" class="cssbtn" id="btn_excel" level="W"><i
|
<button type="button" class="cssbtn" id="btn_excel" level="W"><i
|
||||||
class="material-icons">table_view</i> <%= localeMessage.getString("button.excel") %>
|
class="material-icons">table_view</i> <%= localeMessage.getString("button.excel") %>
|
||||||
</button>
|
</button>
|
||||||
@@ -595,11 +604,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<th style="width:12%; min-width:120px;"><%=localeMessage.getString("tracking.msgrecvtm")%></th>
|
<th style="width:12%; min-width:120px;"><%=localeMessage.getString("tracking.msgrecvtm")%></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:70px; border:1px solid #ebebec;">
|
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="${not empty searchStartYYYYMMDD ? searchStartYYYYMMDD : param.searchStartYYYYMMDD}" size="10" style="width:70px; border:1px solid #ebebec;">
|
||||||
<input type="text" name="searchStartHHMM" value="" size="8" style="width:70px; border:1px solid #ebebec;">
|
<input type="text" name="searchStartHHMM" value="${not empty searchStartHHMM ? searchStartHHMM : param.searchStartHHMM}" size="8" style="width:70px; border:1px solid #ebebec;">
|
||||||
~
|
~
|
||||||
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:70px; border:1px solid #ebebec;">
|
<input type="text" name="searchEndYYYYMMDD" value="${not empty searchEndYYYYMMDD ? searchEndYYYYMMDD : param.searchEndYYYYMMDD}" id="endDatepicker" size="10" readonly="readonly" style="width:70px; border:1px solid #ebebec;">
|
||||||
<input type="text" name="searchEndHHMM" value="" size="8" style="width:70px; border:1px solid #ebebec;">
|
<input type="text" name="searchEndHHMM" value="${not empty searchEndHHMM ? searchEndHHMM : param.searchEndHHMM}" size="8" style="width:70px; border:1px solid #ebebec;">
|
||||||
<input type="hidden" name="searchStartTime" value="" style="width:0px;">
|
<input type="hidden" name="searchStartTime" value="" style="width:0px;">
|
||||||
<input type="hidden" name="searchEndTime" value="" style="width:0px;">
|
<input type="hidden" name="searchEndTime" value="" style="width:0px;">
|
||||||
</td>
|
</td>
|
||||||
@@ -617,8 +626,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<th ><%=localeMessage.getString("tracking.logtype")%></th>
|
<th ><%=localeMessage.getString("tracking.logtype")%></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="radio" name="searchErrorYn" value="N" checked="checked" id="sub2_2_1_1"><label for="sub2_2_1_1"><%= localeMessage.getString("tracking.allLog") %></label>
|
<c:set var="deepLinkErrorYn" value="${not empty searchErrorYn ? searchErrorYn : param.searchErrorYn}" />
|
||||||
<input type="radio" name="searchErrorYn" value="Y" id="sub2_2_1_2"><label for="sub2_2_1_2"><%= localeMessage.getString("tracking.errLog") %></label>
|
<input type="radio" name="searchErrorYn" value="N" ${deepLinkErrorYn eq 'Y' ? '' : 'checked="checked"'} id="sub2_2_1_1"><label for="sub2_2_1_1"><%= localeMessage.getString("tracking.allLog") %></label>
|
||||||
|
<input type="radio" name="searchErrorYn" value="Y" ${deepLinkErrorYn eq 'Y' ? 'checked="checked"' : ''} id="sub2_2_1_2"><label for="sub2_2_1_2"><%= localeMessage.getString("tracking.errLog") %></label>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- 로그조회키 -->
|
<!-- 로그조회키 -->
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -64,6 +67,18 @@ public class MainController implements InterceptorSkipController {
|
|||||||
|
|
||||||
private static final String REDIRECT_LOGIN_URL = "redirect:/emergency.jsp";
|
private static final String REDIRECT_LOGIN_URL = "redirect:/emergency.jsp";
|
||||||
|
|
||||||
|
// 로그인시 target 파라미터로 지정 가능한 딥링크 대상화면 식별자 -> mainPage 경로 매핑
|
||||||
|
// (신규 대상화면 추가시 이 맵에만 등록하면 됨)
|
||||||
|
private static final Map<String, String> DEEPLINK_TARGET_MAIN_PAGE_MAP;
|
||||||
|
static {
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
map.put("tracking", "/onl/transaction/tracking/trackingMan.view"); // 로그조회
|
||||||
|
DEEPLINK_TARGET_MAIN_PAGE_MAP = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 로그인 완료 후 이동할 딥링크 대상화면 식별자를 담아두는 세션 Key명
|
||||||
|
private static final String SESSION_PENDING_DEEPLINK_TARGET = "pendingDeepLinkTarget";
|
||||||
|
|
||||||
private static final String USER_STATUS_LOCKED = "2";
|
private static final String USER_STATUS_LOCKED = "2";
|
||||||
private static final int DEFAULT_MAX_LOGIN_FAIL_COUNT = 5;
|
private static final int DEFAULT_MAX_LOGIN_FAIL_COUNT = 5;
|
||||||
private static final int DEFAULT_PASSWORD_HISTORY_COUNT = 5;
|
private static final int DEFAULT_PASSWORD_HISTORY_COUNT = 5;
|
||||||
@@ -121,9 +136,45 @@ public class MainController implements InterceptorSkipController {
|
|||||||
logger.info("비상 로그인 모드 활성화 - SSO/SMS 인증 우회");
|
logger.info("비상 로그인 모드 활성화 - SSO/SMS 인증 우회");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
savePendingDeepLink(request, session);
|
||||||
|
|
||||||
return "/common/screen/emergency";
|
return "/common/screen/emergency";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그인 페이지에 target 파라미터(딥링크 대상화면 식별자)가 있으면,
|
||||||
|
* 로그인 성공 후 이동할 화면 정보와 그 화면에 전달할 나머지 파라미터를 세션에 임시 저장한다.
|
||||||
|
* target/fullScreen/emergency 를 제외한 나머지 파라미터는 그대로(이름 변경 없이) 전달되므로,
|
||||||
|
* 로그인 URL에는 대상화면이 실제 사용하는 검색조건 파라미터명(예: searchEaiSvcName, searchStartYYYYMMDD)을
|
||||||
|
* 그대로 넘겨야 한다. (실제 사용은 각 대상화면의 컨트롤러가 담당)
|
||||||
|
*/
|
||||||
|
private void savePendingDeepLink(HttpServletRequest request, HttpSession session) {
|
||||||
|
String target = request.getParameter("target");
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(target) || !DEEPLINK_TARGET_MAIN_PAGE_MAP.containsKey(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.setAttribute(SESSION_PENDING_DEEPLINK_TARGET, target);
|
||||||
|
|
||||||
|
Map<String, String> deepLinkParams = new HashMap<>();
|
||||||
|
Enumeration<String> paramNames = request.getParameterNames();
|
||||||
|
|
||||||
|
while (paramNames.hasMoreElements()) {
|
||||||
|
String name = paramNames.nextElement();
|
||||||
|
|
||||||
|
if ("target".equals(name) || "fullScreen".equals(name) || "emergency".equals(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
deepLinkParams.put(name, request.getParameter(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
session.setAttribute(CommonConstants.PENDING_DEEPLINK_PARAMS, deepLinkParams);
|
||||||
|
|
||||||
|
logger.info("딥링크 로그인 요청 - target: " + target);
|
||||||
|
}
|
||||||
|
|
||||||
@RequestMapping("/login.do")
|
@RequestMapping("/login.do")
|
||||||
public String preMain(HttpServletRequest request, LoginVo dto) {
|
public String preMain(HttpServletRequest request, LoginVo dto) {
|
||||||
|
|
||||||
@@ -561,9 +612,35 @@ public class MainController implements InterceptorSkipController {
|
|||||||
if (!processServiceType(request, dto)) { return REDIRECT_LOGIN_URL; }
|
if (!processServiceType(request, dto)) { return REDIRECT_LOGIN_URL; }
|
||||||
|
|
||||||
session.setAttribute(CommonConstants.LOGIN, dto);
|
session.setAttribute(CommonConstants.LOGIN, dto);
|
||||||
|
|
||||||
|
String deepLinkMainPage = consumePendingDeepLinkMainPage(request, session);
|
||||||
|
if (deepLinkMainPage != null) {
|
||||||
|
return "redirect:/main.do?mainPage=" + deepLinkMainPage;
|
||||||
|
}
|
||||||
|
|
||||||
return "redirect:/main.do";
|
return "redirect:/main.do";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 로그인 시 저장해둔 딥링크 대상화면(target)을 1회성으로 꺼내 mainPage 경로로 변환한다.
|
||||||
|
// mainPage 값은 iframe src로 그대로 쓰이는 절대경로이므로 contextPath 를 반드시 포함해야 한다.
|
||||||
|
// (검색조건 등 나머지 파라미터는 CommonConstants.PENDING_DEEPLINK_PARAMS 에 남겨두고,
|
||||||
|
// 대상화면의 컨트롤러가 화면 진입 시점에 직접 소비한다)
|
||||||
|
private String consumePendingDeepLinkMainPage(HttpServletRequest request, HttpSession session) {
|
||||||
|
String target = (String) session.getAttribute(SESSION_PENDING_DEEPLINK_TARGET);
|
||||||
|
session.removeAttribute(SESSION_PENDING_DEEPLINK_TARGET);
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mainPage = DEEPLINK_TARGET_MAIN_PAGE_MAP.get(target);
|
||||||
|
if (mainPage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.getContextPath() + mainPage;
|
||||||
|
}
|
||||||
|
|
||||||
// 사용자 최신 로그인 정보 업데이트
|
// 사용자 최신 로그인 정보 업데이트
|
||||||
private void updateUserInfo(HttpServletRequest request, UserInfo userInfo) {
|
private void updateUserInfo(HttpServletRequest request, UserInfo userInfo) {
|
||||||
String lastLoginIp = IpUtil.getClientIp(request);
|
String lastLoginIp = IpUtil.getClientIp(request);
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ public class CommonConstants {
|
|||||||
|
|
||||||
public static final String LOGIN ="login";
|
public static final String LOGIN ="login";
|
||||||
|
|
||||||
|
// 로그인 딥링크(target 파라미터)로 넘어온 검색조건 등 부가 파라미터를 임시 보관하는 세션 Key명.
|
||||||
|
// 대상화면의 컨트롤러가 화면 진입 시 1회성으로 꺼내 쓰고 세션에서 제거해야 한다.
|
||||||
|
public static final String PENDING_DEEPLINK_PARAMS = "pendingDeepLinkParams";
|
||||||
|
|
||||||
public static final String USER_SERVICETYPE_NO_CHECK = "NO_CHECK";
|
public static final String USER_SERVICETYPE_NO_CHECK = "NO_CHECK";
|
||||||
|
|
||||||
public static final String ROLLING_SUFFIX = "rollingSuffix";
|
public static final String ROLLING_SUFFIX = "rollingSuffix";
|
||||||
|
|||||||
@@ -19,4 +19,6 @@ public interface EAILogRepository {
|
|||||||
|
|
||||||
List<EAILog> getApiLog(Class<? extends EAILog> class1, String apiServiceNumber);
|
List<EAILog> getApiLog(Class<? extends EAILog> class1, String apiServiceNumber);
|
||||||
|
|
||||||
|
String findFirstErrorContent(Class<? extends EAILog> clazz, String apiId, String rangeStart, String rangeEnd);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,4 +261,35 @@ public class EAILogRepositoryImpl implements EAILogRepository {
|
|||||||
.fetch();
|
.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정 구간에서 apiId의 장애(시스템 오류) 로그 중 가장 먼저 발생한 1건의 오류내용(EAIERRCTNT).
|
||||||
|
*
|
||||||
|
* <p>장애 판단 기준은 {@link #appendConditions}의 {@code searchErrorYn=Y} 분기와 동일하다 -
|
||||||
|
* EAIERRCD에 값이 있고, 업무 오류(ERRCODE_BIZ)가 아닌 경우만 장애로 본다.</p>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String findFirstErrorContent(Class<? extends EAILog> clazz, String apiId, String rangeStart, String rangeEnd) {
|
||||||
|
QEAILog qeaiLog = QEAILog.eAILog;
|
||||||
|
EntityPathBase<?> entityPathBase = new EntityPathBase<>(clazz, qeaiLog.getMetadata().getName());
|
||||||
|
|
||||||
|
JPAQuery<String> query = new JPAQueryFactory(em)
|
||||||
|
.select(qeaiLog.eaierrctnt)
|
||||||
|
.from(entityPathBase)
|
||||||
|
.where(qeaiLog.eaisvcname.eq(apiId))
|
||||||
|
.where(qeaiLog.id.msgdpstyms.between(rangeStart, rangeEnd))
|
||||||
|
.where(qeaiLog.eaierrcd.trim().isNotEmpty());
|
||||||
|
|
||||||
|
List<String> bizErrCodes = codeService.findByIdCodegroup(ERRCODE_BIZ_GROUP).stream()
|
||||||
|
.map(code -> code.getId().getCode())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!bizErrCodes.isEmpty()) {
|
||||||
|
query.where(qeaiLog.eaierrcd.notIn(bizErrCodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.orderBy(qeaiLog.id.msgdpstyms.asc(), qeaiLog.id.eaisvcserno.asc(), qeaiLog.id.logprcssserno.asc())
|
||||||
|
.limit(1)
|
||||||
|
.fetchOne();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ public interface EAILogService {
|
|||||||
Map<String, Long> getTransactionCounts(EAILogSearch eaiLogSearch);
|
Map<String, Long> getTransactionCounts(EAILogSearch eaiLogSearch);
|
||||||
|
|
||||||
ApiLogDTO getApiLog(String txDate, String apiServiceNumber);
|
ApiLogDTO getApiLog(String txDate, String apiServiceNumber);
|
||||||
|
|
||||||
|
String findFirstErrorContent(String apiId, String rangeStart, String rangeEnd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,4 +148,11 @@ public class EAILogServiceImpl implements EAILogService {
|
|||||||
return apiLogDTO;
|
return apiLogDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String findFirstErrorContent(String apiId, String rangeStart, String rangeEnd) {
|
||||||
|
long time = CommonUtil.getCalendar(rangeStart).getTimeInMillis();
|
||||||
|
EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time);
|
||||||
|
return eaiLogRepository.findFirstErrorContent(eaiLog.getClass(), apiId, rangeStart, rangeEnd);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,12 +46,10 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
|||||||
+ " FROM ("
|
+ " FROM ("
|
||||||
+ " SELECT A.EAISVCNAME"
|
+ " SELECT A.EAISVCNAME"
|
||||||
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
||||||
// 점검(KIND='MAINTENANCE')만 CTRL_YN 대상이다. KIND 를 안 걸면 관리자가 시작~종료 시각을
|
|
||||||
// 넣어 등록한 장애 공지까지 '점검중'으로 오판해 CHECK_START 가 발생한다.
|
|
||||||
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
|
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
|
||||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
|
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
|
||||||
+ " AND Y.KIND = 'MAINTENANCE'"
|
+ " AND Y.KIND = 'MAINTENANCE'"
|
||||||
+ " AND SYSTIMESTAMP BETWEEN STARTED_AT AND END_AT) "
|
+ " AND :rangeEnd BETWEEN STARTED_AT AND END_AT) "
|
||||||
+ " AND A.EAISVCNAME = X.API_ID),'N') AS CTRL_YN "
|
+ " AND A.EAISVCNAME = X.API_ID),'N') AS CTRL_YN "
|
||||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL >= :errorRate THEN 'Y'"
|
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL >= :errorRate THEN 'Y'"
|
||||||
@@ -61,8 +59,8 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
|||||||
+ " FROM API_STATS_MINUTE"
|
+ " FROM API_STATS_MINUTE"
|
||||||
+ " WHERE A.EAISVCNAME = API_NAME"
|
+ " WHERE A.EAISVCNAME = API_NAME"
|
||||||
+ " AND TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
|
+ " AND TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
|
||||||
+ " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(:errorRangeMinute,'MINUTE'),'YYYYMMDDHH24MI')"
|
+ " BETWEEN TO_CHAR(:errorRangeStart,'YYYYMMDDHH24MI')"
|
||||||
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI'))) AS ERR_YN"
|
+ " AND TO_CHAR(:rangeEnd,'YYYYMMDDHH24MI'))) AS ERR_YN"
|
||||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||||
+ " WHEN RESP_SUM / TOTAL >= :delayAvgRespTime THEN 'Y'"
|
+ " WHEN RESP_SUM / TOTAL >= :delayAvgRespTime THEN 'Y'"
|
||||||
+ " ELSE 'N' END"
|
+ " ELSE 'N' END"
|
||||||
@@ -72,16 +70,17 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
|||||||
+ " FROM API_STATS_MINUTE"
|
+ " FROM API_STATS_MINUTE"
|
||||||
+ " WHERE A.EAISVCNAME = API_NAME"
|
+ " WHERE A.EAISVCNAME = API_NAME"
|
||||||
+ " AND TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
|
+ " AND TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
|
||||||
+ " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(:delayRangeMinute,'MINUTE'),'YYYYMMDDHH24MI')"
|
+ " BETWEEN TO_CHAR(:delayRangeStart,'YYYYMMDDHH24MI')"
|
||||||
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI'))) AS DELAY_YN"
|
+ " AND TO_CHAR(:rangeEnd,'YYYYMMDDHH24MI'))) AS DELAY_YN"
|
||||||
+ " FROM TSEAIHE01 A LEFT OUTER JOIN API_STATUS B ON A.EAISVCNAME = B.EAISVCNAME"
|
+ " FROM TSEAIHE01 A LEFT OUTER JOIN API_STATUS B ON A.EAISVCNAME = B.EAISVCNAME"
|
||||||
+ " )"
|
+ " )"
|
||||||
+ " ) A WHERE EVENT != 'STAY'")
|
+ " ) A WHERE EVENT != 'STAY'")
|
||||||
List<ApiStatusEvent> findApiStatusEvents(
|
List<ApiStatusEvent> findApiStatusEvents(
|
||||||
@Param("errorRate") int errorRate,
|
@Param("errorRate") int errorRate,
|
||||||
@Param("errorRangeMinute") int errorRangeMinute,
|
@Param("delayAvgRespTime") int delayAvgRespTime,
|
||||||
@Param("delayRangeMinute") int delayRangeMinute,
|
@Param("errorRangeStart") LocalDateTime errorRangeStart,
|
||||||
@Param("delayAvgRespTime") int delayAvgRespTime
|
@Param("delayRangeStart") LocalDateTime delayRangeStart,
|
||||||
|
@Param("rangeEnd") LocalDateTime rangeEnd
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package com.eactive.eai.rms.ext.djb.apistatus;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -10,6 +13,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -26,6 +30,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext;
|
|||||||
import com.eactive.eai.rms.data.entity.man.role.Role;
|
import com.eactive.eai.rms.data.entity.man.role.Role;
|
||||||
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiErrorRateRow;
|
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiErrorRateRow;
|
||||||
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent;
|
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent;
|
||||||
|
import com.eactive.eai.rms.data.entity.onl.logger.EAILogService;
|
||||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +52,9 @@ public class ApiStatusService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MonitoringContext monitoringContext;
|
private MonitoringContext monitoringContext;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EAILogService eaiLogService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("transactionManagerForEMS")
|
@Qualifier("transactionManagerForEMS")
|
||||||
private PlatformTransactionManager transactionManager;
|
private PlatformTransactionManager transactionManager;
|
||||||
@@ -70,11 +78,20 @@ public class ApiStatusService {
|
|||||||
|
|
||||||
logErrorRates(errorRate, errorRangeMinute, delayRangeMinute, delayAvgRespTime);
|
logErrorRates(errorRate, errorRangeMinute, delayRangeMinute, delayAvgRespTime);
|
||||||
|
|
||||||
|
// 조회구간을 WAS 시각 기준으로 계산해 쿼리에 직접 바인딩한다.
|
||||||
|
// DB의 SYSDATE 를 기준으로 하면 DB 서버와 WAS 서버의 시각이 어긋날 때 판정구간이
|
||||||
|
// 미묘하게 달라질 수 있고, 알림 메시지에 넣는 로그조회 딥링크의 구간과도 어긋나게 된다.
|
||||||
|
// 장애/지연 판정기준(분)이 서로 다를 수 있으므로 각자의 시작시간을 따로 계산한다.
|
||||||
|
LocalDateTime rangeEnd = LocalDateTime.now();
|
||||||
|
LocalDateTime errorRangeStart = rangeEnd.minusMinutes(errorRangeMinute);
|
||||||
|
LocalDateTime delayRangeStart = rangeEnd.minusMinutes(delayRangeMinute);
|
||||||
|
|
||||||
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
|
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
|
||||||
errorRate,
|
errorRate,
|
||||||
errorRangeMinute,
|
delayAvgRespTime,
|
||||||
delayRangeMinute,
|
errorRangeStart,
|
||||||
delayAvgRespTime
|
delayRangeStart,
|
||||||
|
rangeEnd
|
||||||
);
|
);
|
||||||
|
|
||||||
HashMap<String, List<String>> eventMap = new HashMap<>();
|
HashMap<String, List<String>> eventMap = new HashMap<>();
|
||||||
@@ -112,7 +129,7 @@ public class ApiStatusService {
|
|||||||
|
|
||||||
//내부직원 스윙챗 발송
|
//내부직원 스윙챗 발송
|
||||||
if (monitoringContext.getBooleanProperty(MonitoringContext.DJB_UMS_MESSENGER_APIMONITOR_ENABLED, true)) {
|
if (monitoringContext.getBooleanProperty(MonitoringContext.DJB_UMS_MESSENGER_APIMONITOR_ENABLED, true)) {
|
||||||
HashMap<String, Object> params = getSwingChatMessage(event, apiIds);
|
HashMap<String, Object> params = getSwingChatMessage(event, apiIds, errorRangeStart, delayRangeStart, rangeEnd);
|
||||||
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, params);
|
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +264,8 @@ public class ApiStatusService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private HashMap<String, Object> getSwingChatMessage(String event, List<String> apiIds) {
|
private HashMap<String, Object> getSwingChatMessage(String event, List<String> apiIds,
|
||||||
|
LocalDateTime errorRangeStart, LocalDateTime delayRangeStart, LocalDateTime rangeEnd) {
|
||||||
String message = "";
|
String message = "";
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "CHECK_START":
|
case "CHECK_START":
|
||||||
@@ -272,16 +290,126 @@ public class ApiStatusService {
|
|||||||
message = "";
|
message = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("CHECK_START".equals(event) || "CHECK_END".equals(event)) {
|
||||||
if (apiIds.size() > 1) {
|
if (apiIds.size() > 1) {
|
||||||
message += "\n" + apiIds.get(0) + "외 " + (apiIds.size()-1) + "종";
|
message += "<br/>- " + apiIds.get(0) + "외 " + (apiIds.size()-1) + "종";
|
||||||
} else {
|
} else {
|
||||||
message += "\n- " + apiIds.get(0);
|
message += "<br/>- " + apiIds.get(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message += "<br/>- 인터페이스 : " + String.join(", ", apiIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 장애 발생시에만 첫번째 API ID의 장애 로그(가장 먼저 발생한 1건) 오류내용을 메시지에 붙인다.
|
||||||
|
if ("ERROR_START".equals(event) && !apiIds.isEmpty()) {
|
||||||
|
String errorContent = findFirstErrorContent(apiIds.get(0), errorRangeStart, rangeEnd);
|
||||||
|
if (StringUtils.isNotBlank(errorContent)) {
|
||||||
|
message += "<br/>- 에러메세지 : " + errorContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 장애/지연 발생시에만 첫번째 API ID로 로그조회 화면 바로가기 링크를 메시지 끝에 추가한다.
|
||||||
|
// (장애/지연은 판정기준 시간간격이 서로 다를 수 있어 각자의 판정구간을 사용한다)
|
||||||
|
// UmsService.makeBodyData() 가 이 마커 뒤의 내용을 URL로 보고 본문에서 떼어내 확인URL(PC) 필드로 옮긴다.
|
||||||
|
LocalDateTime linkRangeStart = "ERROR_START".equals(event) ? errorRangeStart
|
||||||
|
: "DELAY_START".equals(event) ? delayRangeStart
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (linkRangeStart != null) {
|
||||||
|
String loginUrl = buildTrackingLoginUrl(linkRangeStart, rangeEnd);
|
||||||
|
if (loginUrl != null) {
|
||||||
|
message += "<br/>- 로그조회링크 : " + loginUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap<String, Object> params = new HashMap<>();
|
HashMap<String, Object> params = new HashMap<>();
|
||||||
params.put("message", message);
|
params.put("message", message);
|
||||||
|
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DEEPLINK_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||||
|
private static final DateTimeFormatter DEEPLINK_TIME_FORMATTER = DateTimeFormatter.ofPattern("HHmmss");
|
||||||
|
|
||||||
|
// MSGDPSTYMS(로그 테이블 PK 일부) 형식과 동일 - yyyyMMddHHmmss + 밀리초(3자리)
|
||||||
|
private static final DateTimeFormatter LOG_RANGE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||||
|
|
||||||
|
// WAR는 항상 이 context path로 배포된다 (readme.md/build.gradle 기준)
|
||||||
|
private static final String ADMIN_CONTEXT_PATH = "/monitoring";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 판정 구간에서 apiId의 장애 로그 중 가장 먼저 발생한 1건의 오류내용(EAIERRCTNT).
|
||||||
|
*
|
||||||
|
* <p>장애 판단 기준은 {@code EAILogRepositoryImpl}의 에러로그 검색 조건과 동일 - EAIERRCD에 값이 있고
|
||||||
|
* 업무 오류(공통코드 ERRCODE_BIZ)가 아닌 경우만 장애로 본다.</p>
|
||||||
|
*
|
||||||
|
* <p>알림 발송 자체를 막아서는 안 되므로 조회 실패는 로그만 남기고 삼킨다.</p>
|
||||||
|
*/
|
||||||
|
private String findFirstErrorContent(String apiId, LocalDateTime rangeStart, LocalDateTime rangeEnd) {
|
||||||
|
try {
|
||||||
|
String start = rangeStart.format(LOG_RANGE_FORMATTER) + "000";
|
||||||
|
String end = rangeEnd.format(LOG_RANGE_FORMATTER) + "999";
|
||||||
|
return eaiLogService.findFirstErrorContent(apiId, start, end);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("장애 로그 조회 실패 - {}", apiId, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리콘솔 로그인 화면으로 이동해 로그인 완료 즉시 로그조회 화면에서
|
||||||
|
* 해당 시간구간의 에러로그로 자동조회 되도록 하는 딥링크 URL을 만든다.
|
||||||
|
* (로그조회 화면 파라미터명과 동일해야 하며, {@code MainController.loginForm.do} 의
|
||||||
|
* target=tracking 딥링크 처리와 짝을 이룬다)
|
||||||
|
*
|
||||||
|
* <p>API ID는 여러 건일 수 있어 검색조건으로 넣지 않고, 대신 장애 로그(searchErrorYn=Y)로만 좁힌다.</p>
|
||||||
|
*
|
||||||
|
* @return 현재 서버 IP에 대한 WEBSERVERPORT 프로퍼티를 못 찾으면 null
|
||||||
|
*/
|
||||||
|
private String buildTrackingLoginUrl(LocalDateTime rangeStart, LocalDateTime rangeEnd) {
|
||||||
|
String baseUrl = resolveCurrentServerBaseUrl();
|
||||||
|
if (baseUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder url = new StringBuilder(baseUrl);
|
||||||
|
url.append("/loginForm.do?target=tracking");
|
||||||
|
url.append("&searchErrorYn=Y");
|
||||||
|
url.append("&searchStartYYYYMMDD=").append(rangeStart.format(DEEPLINK_DATE_FORMATTER));
|
||||||
|
url.append("&searchStartHHMM=").append(rangeStart.format(DEEPLINK_TIME_FORMATTER));
|
||||||
|
url.append("&searchEndYYYYMMDD=").append(rangeEnd.format(DEEPLINK_DATE_FORMATTER));
|
||||||
|
url.append("&searchEndHHMM=").append(rangeEnd.format(DEEPLINK_TIME_FORMATTER));
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 서버(이 WAS가 뜬 인스턴스)의 IP로, 모니터링 프로퍼티 그룹명이
|
||||||
|
* {@code Monitoring{<ip>}} 형태로 등록된 WEBSERVERPORT 프로퍼티 값을 찾아 base URL을 만든다.
|
||||||
|
* ({@link MonitoringContext#getPortMapper()}가 이미 그룹명에서 IP를 뽑아 IP -> 포트로
|
||||||
|
* 매핑해두므로 그대로 재사용한다. {@code EaiServerInfoServiceImpl}과 동일한 조회 방식)
|
||||||
|
*
|
||||||
|
* @return WEBSERVERPORT 프로퍼티가 없으면 null
|
||||||
|
*/
|
||||||
|
private String resolveCurrentServerBaseUrl() {
|
||||||
|
String ip;
|
||||||
|
try {
|
||||||
|
ip = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
log.warn("현재 서버 IP 조회 실패 - 로그조회 딥링크 생략", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> portMapper = monitoringContext.getPortMapper();
|
||||||
|
String port = (portMapper != null) ? portMapper.get(ip) : null;
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(port)) {
|
||||||
|
log.warn("WEBSERVERPORT 프로퍼티 없음 (Monitoring{" + ip + "}) - 로그조회 딥링크 생략");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "http://" + ip + ":" + port + ADMIN_CONTEXT_PATH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ public class UmsService {
|
|||||||
private static final String BZWK_CD = "EBK"; //업무코드
|
private static final String BZWK_CD = "EBK"; //업무코드
|
||||||
private static final String SUB_BZWK_CD = "EBG"; //서브업무코드
|
private static final String SUB_BZWK_CD = "EBG"; //서브업무코드
|
||||||
|
|
||||||
|
// message 끝에 붙는 확인URL(PC) 마커. 이 뒤의 내용은 URL로 간주해 본문에서 떼어낸다.
|
||||||
|
private static final String CHECK_URL_FOR_PC_MARKER = "<br/>- 로그조회링크 : ";
|
||||||
|
|
||||||
|
|
||||||
private final MonitoringPropertyService propertyService;
|
private final MonitoringPropertyService propertyService;
|
||||||
private final MessageRequestService messageRequestService;
|
private final MessageRequestService messageRequestService;
|
||||||
@@ -256,7 +259,21 @@ public class UmsService {
|
|||||||
SwingDataVO swiDataVO = new SwingDataVO();
|
SwingDataVO swiDataVO = new SwingDataVO();
|
||||||
swiDataVO.setNotiCode("CNO_FEP_API_0002"); //0001:긴급, 0002:중요
|
swiDataVO.setNotiCode("CNO_FEP_API_0002"); //0001:긴급, 0002:중요
|
||||||
swiDataVO.setNotiTitle("알림");
|
swiDataVO.setNotiTitle("알림");
|
||||||
swiDataVO.setNotiContent(messageRequest.getMessage());
|
|
||||||
|
// message 끝에 "로그조회링크 : URL" 이 붙어 있으면(ApiStatusService 참고) 본문에서는
|
||||||
|
// 떼어내고 URL만 뽑아 확인URL(PC) 필드에 따로 담는다.
|
||||||
|
String notiContent = messageRequest.getMessage();
|
||||||
|
String checkUrlForPc = "";
|
||||||
|
|
||||||
|
int checkUrlForPcIdx = notiContent.indexOf(CHECK_URL_FOR_PC_MARKER);
|
||||||
|
if (checkUrlForPcIdx >= 0) {
|
||||||
|
checkUrlForPc = notiContent.substring(checkUrlForPcIdx + CHECK_URL_FOR_PC_MARKER.length()).trim();
|
||||||
|
notiContent = notiContent.substring(0, checkUrlForPcIdx).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
swiDataVO.setNotiContent(notiContent);
|
||||||
|
swiDataVO.setCheckUrlForPc(checkUrlForPc);
|
||||||
|
|
||||||
|
|
||||||
// 스윙챗 알림 데이터
|
// 스윙챗 알림 데이터
|
||||||
swiDataVO.setReceiverInfo(getSwingReceiver(messageRequest));
|
swiDataVO.setReceiverInfo(getSwingReceiver(messageRequest));
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ public class SwingDataVO {
|
|||||||
/** 알림내용 */
|
/** 알림내용 */
|
||||||
private String notiContent;
|
private String notiContent;
|
||||||
|
|
||||||
|
/** 확인URL(PC) */
|
||||||
|
private String checkUrlForPc;
|
||||||
|
|
||||||
/** 수신자정보 */
|
/** 수신자정보 */
|
||||||
private List<SwingReceiverDataVO> receiverInfo;
|
private List<SwingReceiverDataVO> receiverInfo;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-1
@@ -8,6 +8,7 @@ import com.eactive.eai.data.entity.onl.server.EAIServer;
|
|||||||
import com.eactive.eai.rms.common.combo.ComboVo;
|
import com.eactive.eai.rms.common.combo.ComboVo;
|
||||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||||
import com.eactive.eai.rms.common.logging.UserAccessLogger;
|
import com.eactive.eai.rms.common.logging.UserAccessLogger;
|
||||||
|
import com.eactive.eai.rms.common.util.CommonConstants;
|
||||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||||
import com.eactive.eai.rms.common.vo.GridResponse;
|
import com.eactive.eai.rms.common.vo.GridResponse;
|
||||||
import com.eactive.eai.rms.data.entity.onl.adapter.AdapterGroupService;
|
import com.eactive.eai.rms.data.entity.onl.adapter.AdapterGroupService;
|
||||||
@@ -42,9 +43,11 @@ import org.springframework.web.client.HttpStatusCodeException;
|
|||||||
import org.springframework.web.client.ResourceAccessException;
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
import org.springframework.ui.ModelMap;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -73,7 +76,20 @@ public class DbTrackingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/onl/transaction/tracking/trackingMan.view")
|
@GetMapping(value = "/onl/transaction/tracking/trackingMan.view")
|
||||||
public String viewList() {
|
public String viewList(HttpServletRequest request, ModelMap model) {
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
|
||||||
|
// 로그인 딥링크(target=tracking)로 넘어온 검색조건 파라미터를 1회성으로 소비한다.
|
||||||
|
// 파라미터명은 이 화면의 검색조건 input name(searchEaiSvcName, searchStartYYYYMMDD 등)과 동일해야 하며,
|
||||||
|
// 그 이름 그대로 모델에 등록해 JSP에서 파라미터명 그대로(${searchXXX}) 꺼내 쓸 수 있게 한다.
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, String> pendingParams = (Map<String, String>) session.getAttribute(CommonConstants.PENDING_DEEPLINK_PARAMS);
|
||||||
|
|
||||||
|
if (pendingParams != null) {
|
||||||
|
session.removeAttribute(CommonConstants.PENDING_DEEPLINK_PARAMS);
|
||||||
|
model.addAllAttributes(pendingParams);
|
||||||
|
}
|
||||||
|
|
||||||
return "/onl/transaction/tracking/trackingMan";
|
return "/onl/transaction/tracking/trackingMan";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user