Compare commits
19 Commits
d19a9eb593
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bf7f661e3 | |||
| 124df00f51 | |||
| e4de8ebc8b | |||
| 52e8c3be39 | |||
| 136626e7c4 | |||
| 0848cd3469 | |||
| 0b6f273132 | |||
| 266644798f | |||
| 2e8b45c69e | |||
| ea3d23424c | |||
| 2a9dfaa093 | |||
| 3b74f9ce38 | |||
| 145ad37499 | |||
| a235b0656c | |||
| da397b21b0 | |||
| e084436c90 | |||
| 15652e158d | |||
| 8e4c2efe88 | |||
| cad51df3cb |
@@ -1,7 +1,7 @@
|
|||||||
UserManController_사용자관리_APIGW_INSERT,UPDATE,DELETE
|
UserManController_사용자관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
AdapterController_어댑터관리_APIGW_INSERT,UPDATE,DELETE
|
AdapterController_어댑터관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
ApiInterfaceController_API관리_APIGW_INSERT,UPDATE,DELETE,CLONE
|
ApiInterfaceController_API관리_APIGW_INSERT,UPDATE,DELETE,CLONE
|
||||||
ClientController_Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE
|
ClientController_Client (키정보)관리_APIGW_INSERT,UPDATE,DELETE,SYNC_PORTAL
|
||||||
RoleController_Role (역할)관리_APIGW_INSERT,UPDATE,DELETE
|
RoleController_Role (역할)관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
MenuController_Menu관리_APIGW_INSERT,UPDATE,DELETE
|
MenuController_Menu관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
PortalApprovalLineManController_승인라인관리_APIGW_INSERT,UPDATE,DELETE
|
PortalApprovalLineManController_승인라인관리_APIGW_INSERT,UPDATE,DELETE
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
// (enricher 는 oauth·api_key 를 모두 apiKey-in-header 로 모델링하므로 scheme.type 만으로 구분 불가)
|
// (enricher 는 oauth·api_key 를 모두 apiKey-in-header 로 모델링하므로 scheme.type 만으로 구분 불가)
|
||||||
var djbAuthType = '';
|
var djbAuthType = '';
|
||||||
|
|
||||||
|
// 마지막 저장(또는 최초 로드) 시점의 state.data 스냅샷 — 닫기 전 미저장 변경 여부 판별용.
|
||||||
|
var savedSnapshot = null;
|
||||||
|
function isDirty() { return savedSnapshot !== null && JSON.stringify(state.data) !== savedSnapshot; }
|
||||||
|
function closeEditor() {
|
||||||
|
if (isDirty() && !window.confirm('저장하지 않은 변경사항이 있습니다. 저장하지 않고 닫으시겠습니까?')) return;
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 2. 유틸
|
// 2. 유틸
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -1703,7 +1711,7 @@
|
|||||||
|
|
||||||
var bSave = $('#btn-save'); if (bSave) bSave.addEventListener('click', () => saveSpec(false));
|
var bSave = $('#btn-save'); if (bSave) bSave.addEventListener('click', () => saveSpec(false));
|
||||||
var bRegen = $('#btn-regen'); if (bRegen) bRegen.addEventListener('click', regenerate);
|
var bRegen = $('#btn-regen'); if (bRegen) bRegen.addEventListener('click', regenerate);
|
||||||
var bClose = $('#btn-close'); if (bClose) bClose.addEventListener('click', () => window.close());
|
var bClose = $('#btn-close'); if (bClose) bClose.addEventListener('click', closeEditor);
|
||||||
|
|
||||||
// Cmd/Ctrl + S → 저장 (브라우저 기본 저장 대화상자 차단)
|
// Cmd/Ctrl + S → 저장 (브라우저 기본 저장 대화상자 차단)
|
||||||
document.addEventListener('keydown', function (e) {
|
document.addEventListener('keydown', function (e) {
|
||||||
@@ -2066,6 +2074,7 @@
|
|||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (res && res.status === 'success') {
|
if (res && res.status === 'success') {
|
||||||
toast(temp ? '임시저장되었습니다' : '저장되었습니다');
|
toast(temp ? '임시저장되었습니다' : '저장되었습니다');
|
||||||
|
savedSnapshot = JSON.stringify(state.data); // 저장 성공 시점을 새 저장 기준선으로 갱신
|
||||||
var badge = $('#hdr-source'); if (badge) { badge.textContent = '저장본'; badge.className = 'ml-1 px-2 py-0.5 text-[11px] rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200'; badge.classList.remove('hidden'); }
|
var badge = $('#hdr-source'); if (badge) { badge.textContent = '저장본'; badge.className = 'ml-1 px-2 py-0.5 text-[11px] rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200'; badge.classList.remove('hidden'); }
|
||||||
} else toast('저장 실패: ' + (res && res.errorMsg || ''), 'error');
|
} else toast('저장 실패: ' + (res && res.errorMsg || ''), 'error');
|
||||||
})
|
})
|
||||||
@@ -2102,6 +2111,7 @@
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
function init() {
|
function init() {
|
||||||
loadInitialData().then(function () {
|
loadInitialData().then(function () {
|
||||||
|
savedSnapshot = JSON.stringify(state.data); // 초기 로드(자동생성 포함) 완료 시점을 저장 기준선으로 설정
|
||||||
renderStepper();
|
renderStepper();
|
||||||
renderForm();
|
renderForm();
|
||||||
bindGlobalEvents();
|
bindGlobalEvents();
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
data: $('#loginForm').serialize(),
|
data: $('#loginForm').serialize(),
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
|
console.log('res', response)
|
||||||
if (response.changePassword) {
|
if (response.changePassword) {
|
||||||
showChangeInitPassword(response);
|
showChangeInitPassword(response);
|
||||||
}
|
}
|
||||||
@@ -115,10 +116,16 @@
|
|||||||
} else if (response.success) {
|
} else if (response.success) {
|
||||||
// 직접 로그인 성공
|
// 직접 로그인 성공
|
||||||
window.location.href = response.redirectUrl;
|
window.location.href = response.redirectUrl;
|
||||||
|
} else {
|
||||||
|
if (response.errorMessage == 'NO_PHONE_NUMBER') {
|
||||||
|
alert("휴대폰번호가 등록되어 있지 않아 로그인할 수 없습니다. 관리자에게 문의하세요")
|
||||||
|
} else if (response.errorMessage == 'FAIL_SEND_AUTH') {
|
||||||
|
alert("인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.")
|
||||||
} else {
|
} else {
|
||||||
// 로그인 실패
|
// 로그인 실패
|
||||||
alert('로그인에 실패했습니다');
|
alert('로그인에 실패했습니다');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
|
|||||||
@@ -308,7 +308,8 @@
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: {
|
data: {
|
||||||
cmd: 'SYNC_PORTAL',
|
cmd: 'SYNC_PORTAL',
|
||||||
clientId: clientId
|
clientId: clientId,
|
||||||
|
serviceType: 'APIGW'
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
showSyncResultModal(response);
|
showSyncResultModal(response);
|
||||||
@@ -453,8 +454,8 @@
|
|||||||
</div><!-- end content_top -->
|
</div><!-- end content_top -->
|
||||||
<div class="content_middle">
|
<div class="content_middle">
|
||||||
<div class="search_wrap">
|
<div class="search_wrap">
|
||||||
<!-- <button type="button" class="cssbtn" id="btn_sync_portal" level="W" status="DETAIL"><i class="material-icons">sync</i> 개발자포탈 정보 반영
|
<button type="button" class="cssbtn" id="btn_sync_portal" level="W" status="DETAIL"><i class="material-icons">sync</i> 개발자포탈 정보 반영
|
||||||
</button> -->
|
</button>
|
||||||
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL"><i class="material-icons">delete</i> <%= localeMessage.getString("button.delete") %>
|
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL"><i class="material-icons">delete</i> <%= localeMessage.getString("button.delete") %>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="cssbtn" id="btn_modify" level="W" status="DETAIL,NEW"><i
|
<button type="button" class="cssbtn" id="btn_modify" level="W" status="DETAIL,NEW"><i
|
||||||
@@ -741,7 +742,7 @@ function showSyncResultModal(response) {
|
|||||||
if (response.success) {
|
if (response.success) {
|
||||||
statusClass = "status-success";
|
statusClass = "status-success";
|
||||||
var createdCount = response.createdApiSpecCount || 0;
|
var createdCount = response.createdApiSpecCount || 0;
|
||||||
var createdMsg = createdCount > 0 ? "<br/><span style='color:#007bff;'>* " + createdCount + "개의 API Spec이 자동 생성되었습니다.</span>" : "";
|
var createdMsg = createdCount > 0 ? "<br/><span style='color:#007bff;'>* " + createdCount + "개의 API Spec을 비공개(N)로 자동 생성했습니다. API Spec 관리에서 확인 후 공개하세요.</span>" : "";
|
||||||
|
|
||||||
if (response.action === 'INSERTED') {
|
if (response.action === 'INSERTED') {
|
||||||
statusMsg = "<strong>반영 완료</strong> - 포탈에 새로운 인증정보가 생성되었습니다." + createdMsg;
|
statusMsg = "<strong>반영 완료</strong> - 포탈에 새로운 인증정보가 생성되었습니다." + createdMsg;
|
||||||
@@ -771,6 +772,7 @@ function showSyncResultModal(response) {
|
|||||||
var syncedApis = response.syncedApis || [];
|
var syncedApis = response.syncedApis || [];
|
||||||
var invalidApis = response.invalidApis || [];
|
var invalidApis = response.invalidApis || [];
|
||||||
var createdApiSpecs = response.createdApiSpecs || [];
|
var createdApiSpecs = response.createdApiSpecs || [];
|
||||||
|
var specGenFailed = response.specGenFailed || [];
|
||||||
|
|
||||||
$targetCount.text(targetApis.length);
|
$targetCount.text(targetApis.length);
|
||||||
|
|
||||||
@@ -784,8 +786,11 @@ function showSyncResultModal(response) {
|
|||||||
if (invalidApis.includes(apiId)) {
|
if (invalidApis.includes(apiId)) {
|
||||||
status = "미존재 (APIGW에 없음)";
|
status = "미존재 (APIGW에 없음)";
|
||||||
statusClass = "api-invalid";
|
statusClass = "api-invalid";
|
||||||
|
} else if (specGenFailed.includes(apiId)) {
|
||||||
|
status = "반영됨 (Spec 자동생성 실패·빈 항목)";
|
||||||
|
statusClass = "api-created";
|
||||||
} else if (createdApiSpecs.includes(apiId)) {
|
} else if (createdApiSpecs.includes(apiId)) {
|
||||||
status = "반영됨 (Spec 신규생성)";
|
status = "반영됨 (Spec 자동생성·비공개)";
|
||||||
statusClass = "api-created";
|
statusClass = "api-created";
|
||||||
} else if (syncedApis.includes(apiId)) {
|
} else if (syncedApis.includes(apiId)) {
|
||||||
status = "반영됨";
|
status = "반영됨";
|
||||||
|
|||||||
@@ -47,6 +47,8 @@
|
|||||||
$('#sentDate').text(data.sentDateFull || '');
|
$('#sentDate').text(data.sentDateFull || '');
|
||||||
// 서버에서 마스킹+escape 된 안전 HTML
|
// 서버에서 마스킹+escape 된 안전 HTML
|
||||||
$('#messageHtml').html(data.messageHtml || '');
|
$('#messageHtml').html(data.messageHtml || '');
|
||||||
|
// 응답내용(RESPONSE_DATA) — 원문이므로 text 로만 출력
|
||||||
|
$('#responseData').text(data.responseData || '');
|
||||||
|
|
||||||
// PENDING 건만 '실패 처리' 노출 (권한 제어는 buttonControl 이 담당)
|
// PENDING 건만 '실패 처리' 노출 (권한 제어는 buttonControl 이 담당)
|
||||||
if (data.requestStatus !== 'PENDING') {
|
if (data.requestStatus !== 'PENDING') {
|
||||||
@@ -142,6 +144,10 @@
|
|||||||
<th>메세지 내용</th>
|
<th>메세지 내용</th>
|
||||||
<td colspan="3"><div id="messageHtml" class="msg-detail"></div></td>
|
<td colspan="3"><div id="messageHtml" class="msg-detail"></div></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>응답내용</th>
|
||||||
|
<td colspan="3"><div id="responseData" class="msg-detail"></div></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@
|
|||||||
var url = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.json"/>';
|
var url = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.json"/>';
|
||||||
var url_view = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.view"/>';
|
var url_view = '<c:url value="/onl/kjb/statistics/apiStatsHourMan.view"/>';
|
||||||
var url_minute_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>';
|
var url_minute_view = '<c:url value="/onl/kjb/statistics/apiStatsMinuteMan.view"/>';
|
||||||
|
var url_left_menu = '<c:url value="/leftMenu.do"/>';
|
||||||
|
var DASHBOARD_MENU_ID = '0305001'; // apiStatsMinuteMan(대시보드) 메뉴 ID
|
||||||
|
|
||||||
var totalDonutChart, callChart;
|
var totalDonutChart, callChart;
|
||||||
|
|
||||||
@@ -162,23 +164,20 @@
|
|||||||
var hour = parseInt(statTime.substring(8, 10));
|
var hour = parseInt(statTime.substring(8, 10));
|
||||||
|
|
||||||
var startDate = year + '-' + month + '-' + day;
|
var startDate = year + '-' + month + '-' + day;
|
||||||
var startTime = (hour < 10 ? '0' + hour : hour) + ':00';
|
var hourStr = (hour < 10 ? '0' + hour : hour);
|
||||||
|
var startTime = hourStr + ':00';
|
||||||
// 1시간 후 계산 (23시인 경우 다음날 00시로 자동 계산)
|
// 해당 시간대 끝(정시~59분)으로 이동 (예: 10:00~10:59)
|
||||||
var d = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), hour, 0, 0);
|
var endTime = hourStr + ':59';
|
||||||
d.setHours(d.getHours() + 1);
|
|
||||||
|
|
||||||
var endDate = d.getFullYear() + '-' +
|
|
||||||
(d.getMonth() + 1 < 10 ? '0' : '') + (d.getMonth() + 1) + '-' +
|
|
||||||
(d.getDate() < 10 ? '0' : '') + d.getDate();
|
|
||||||
var endTime = (d.getHours() < 10 ? '0' : '') + d.getHours() + ':00';
|
|
||||||
|
|
||||||
var params = '?searchStartDate=' + startDate + '&searchStartTime=' + startTime
|
var params = '?searchStartDate=' + startDate + '&searchStartTime=' + startTime
|
||||||
+ '&searchEndDate=' + endDate + '&searchEndTime=' + endTime;
|
+ '&searchEndDate=' + startDate + '&searchEndTime=' + endTime;
|
||||||
params += '&menuId='+'${param.menuId}';
|
params += '&menuId='+DASHBOARD_MENU_ID;
|
||||||
params += '&cmd='+'LIST';
|
params += '&cmd='+'LIST';
|
||||||
params += '&serviceType='+'APIGW';
|
params += '&serviceType='+'APIGW';
|
||||||
|
|
||||||
|
// 왼쪽 메뉴도 대시보드(분단위 통계) 메뉴로 전환
|
||||||
|
parent.leftFrame.location.href = url_left_menu + '?menuId=' + DASHBOARD_MENU_ID
|
||||||
|
+ '&serviceType=' + sessionStorage["serviceType"];
|
||||||
location.href = url_minute_view + params;
|
location.href = url_minute_view + params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,14 +262,14 @@
|
|||||||
var diffMinutes = (end - start) / (1000 * 60);
|
var diffMinutes = (end - start) / (1000 * 60);
|
||||||
|
|
||||||
if (diffMinutes > 60) {
|
if (diffMinutes > 60) {
|
||||||
// 1시간 초과 시 시작 + 1시간으로 조정
|
// 1시간 초과 시 시작시각 기준 1시간치(정시~59분)로 조정
|
||||||
endDateTime = updateEndTime(new Date(start.getTime() + 60 * 60 * 1000));
|
endDateTime = updateEndTime(new Date(start.getTime() + 59 * 60 * 1000));
|
||||||
} else {
|
} else {
|
||||||
endDateTime = formatDateTime(end);
|
endDateTime = formatDateTime(end);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 종료시간 미입력 시 시작 + 1시간으로 설정
|
// 종료시간 미입력 시 시작시각 기준 1시간치(정시~59분)로 설정
|
||||||
endDateTime = updateEndTime(new Date(start.getTime() + 60 * 60 * 1000));
|
endDateTime = updateEndTime(new Date(start.getTime() + 59 * 60 * 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { start: startDateTime, end: endDateTime };
|
return { start: startDateTime, end: endDateTime };
|
||||||
@@ -325,8 +325,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 조회기간을 정시 단위 1시간 구간으로 이동 (direction: -1 이전, 1 이후)
|
// 조회기간을 정시 단위 1시간 구간으로 이동 (direction: -1 이전, 1 이후)
|
||||||
// ex) 시작 08:35, 이전 클릭 -> 08:00~09:00, 다시 -> 07:00~08:00
|
// ex) 시작 08:35, 이전 클릭 -> 08:00~08:59, 다시 -> 07:00~07:59
|
||||||
// ex) 시작 08:35, 이후 클릭 -> 09:00~10:00, 다시 -> 10:00~11:00
|
// ex) 시작 08:35, 이후 클릭 -> 09:00~09:59, 다시 -> 10:00~10:59
|
||||||
function shiftTimeRange(direction) {
|
function shiftTimeRange(direction) {
|
||||||
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
||||||
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
|
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
|
||||||
@@ -350,7 +350,8 @@
|
|||||||
start.setHours(start.getHours() + 1);
|
start.setHours(start.getHours() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var end = new Date(start.getTime() + 60 * 60 * 1000);
|
// 종료시각은 같은 시간대의 59분으로 설정 (예: 10:00~10:59)
|
||||||
|
var end = new Date(start.getTime() + 59 * 60 * 1000);
|
||||||
|
|
||||||
function pad(n) { return String(n).padStart(2, '0'); }
|
function pad(n) { return String(n).padStart(2, '0'); }
|
||||||
|
|
||||||
@@ -580,9 +581,9 @@
|
|||||||
|
|
||||||
$("input[name=searchStartDate]").datepicker();
|
$("input[name=searchStartDate]").datepicker();
|
||||||
|
|
||||||
// 기본값 설정 (현재 시간(분) 기준 1시간 전 ~ 현재 시간(분))
|
// 기본값 설정 (현재 시간(분) 기준 59분 전 ~ 현재 시간(분))
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
var oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
var oneHourAgo = new Date(now.getTime() - 59 * 60 * 1000);
|
||||||
|
|
||||||
function formatDate(d) {
|
function formatDate(d) {
|
||||||
return d.getFullYear() + '-' +
|
return d.getFullYear() + '-' +
|
||||||
|
|||||||
@@ -38,6 +38,13 @@
|
|||||||
success: function (json) {
|
success: function (json) {
|
||||||
new makeOptions("BIZCODE", "BIZNAME").setObj($("select[name=searchEaiBzwkDstcd]")).setNoValueInclude(true).setNoValue("", "전체").setData(json.bizList).setFormat(codeName3OptionFormat).rendering();
|
new makeOptions("BIZCODE", "BIZNAME").setObj($("select[name=searchEaiBzwkDstcd]")).setNoValueInclude(true).setNoValue("", "전체").setData(json.bizList).setFormat(codeName3OptionFormat).rendering();
|
||||||
|
|
||||||
|
var apiGroupSelect = $('select[name=searchApiGroupId]').empty();
|
||||||
|
apiGroupSelect.append($('<option>').val('').text('전체'));
|
||||||
|
apiGroupSelect.append($('<option>').val('UNREGISTERED').text('미등록'));
|
||||||
|
(json.apiGroupList || []).forEach(function (group) {
|
||||||
|
apiGroupSelect.append($('<option>').val(group.id).text(group.groupName));
|
||||||
|
});
|
||||||
|
|
||||||
setSearchable(selectName); // 콤보에 searchable 설정
|
setSearchable(selectName); // 콤보에 searchable 설정
|
||||||
putSelectFromParam();
|
putSelectFromParam();
|
||||||
|
|
||||||
@@ -86,6 +93,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function specStatusFormatter(cellvalue, options, rowObject) {
|
||||||
|
switch (cellvalue) {
|
||||||
|
case 'PUBLISHED':
|
||||||
|
var groupNames = rowObject.publishedGroupNames || [];
|
||||||
|
var tooltip = groupNames.length ? groupNames.join('\n') : '게시된 그룹 없음';
|
||||||
|
return $('<span>').text('게시(' + groupNames.length + ')').attr('title', tooltip).css('cursor', 'help').prop('outerHTML');
|
||||||
|
case 'REGISTERED': return '등록';
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function list() {
|
function list() {
|
||||||
detail()
|
detail()
|
||||||
var gridPostData = getSearchForJqgrid("cmd", "LIST"); //jqgrid에서는 object 로
|
var gridPostData = getSearchForJqgrid("cmd", "LIST"); //jqgrid에서는 object 로
|
||||||
@@ -101,6 +119,7 @@
|
|||||||
'요청',
|
'요청',
|
||||||
'응답',
|
'응답',
|
||||||
'상태',
|
'상태',
|
||||||
|
'스펙 상태',
|
||||||
'작성자',
|
'작성자',
|
||||||
'가상응답여부',
|
'가상응답여부',
|
||||||
'변경일시(*)',
|
'변경일시(*)',
|
||||||
@@ -113,6 +132,7 @@
|
|||||||
{name: 'fromAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
|
{name: 'fromAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
|
||||||
{name: 'toAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
|
{name: 'toAdapter', align: 'center', width: '30', formatter: adapterNameShortFormatter, sortable: false},
|
||||||
{name: 'statusCode', align: 'center', width: '30', formatter: apiStatusFormatter, sortable: false},
|
{name: 'statusCode', align: 'center', width: '30', formatter: apiStatusFormatter, sortable: false},
|
||||||
|
{name: 'specStatus', align: 'center', width: '40', formatter: specStatusFormatter, sortable: false, title: false},
|
||||||
{name: 'author', align: 'center', width: '40', sortable: false},
|
{name: 'author', align: 'center', width: '40', sortable: false},
|
||||||
{name : 'simYn', align : 'center' , width:'40', hidden: true },
|
{name : 'simYn', align : 'center' , width:'40', hidden: true },
|
||||||
{name : 'lastModifiedDate', align : 'center' , width:'60', sortable: true},
|
{name : 'lastModifiedDate', align : 'center' , width:'60', sortable: true},
|
||||||
@@ -421,6 +441,14 @@
|
|||||||
<td>
|
<td>
|
||||||
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}"></select>
|
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}"></select>
|
||||||
</td>
|
</td>
|
||||||
|
<th style="width:180px;">당타발 구분</th>
|
||||||
|
<td>
|
||||||
|
<select name="searchInOutType" style="width: 100%">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="1">당발</option>
|
||||||
|
<option value="2">타발</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
<th style="width:180px;">상태</th>
|
<th style="width:180px;">상태</th>
|
||||||
<td>
|
<td>
|
||||||
<select name="searchStatusCode" style="width: 100%">
|
<select name="searchStatusCode" style="width: 100%">
|
||||||
@@ -431,6 +459,24 @@
|
|||||||
<option value="C">점검</option>
|
<option value="C">점검</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th style="width:180px;">API 스펙 상태</th>
|
||||||
|
<td>
|
||||||
|
<select name="searchSpecStatus" style="width: 100%">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="UNREGISTERED">미등록</option>
|
||||||
|
<option value="REGISTERED">등록</option>
|
||||||
|
<option value="PUBLISHED">게시</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<th style="width:180px;">API 그룹</th>
|
||||||
|
<td>
|
||||||
|
<select name="searchApiGroupId" style="width: 100%">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="UNREGISTERED">미등록</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
<th></th><td></td>
|
<th></th><td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
+13
-3
@@ -113,9 +113,9 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
|
// https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
|
||||||
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
|
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
|
||||||
|
|
||||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.1'
|
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.2.2'
|
||||||
//implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
|
//implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
|
||||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1'
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2'
|
||||||
|
|
||||||
// apistatus-draft.yml (자동 탐지 초안 양식) 로딩용.
|
// apistatus-draft.yml (자동 탐지 초안 양식) 로딩용.
|
||||||
// admin 은 Spring Boot 가 아니라 yml 자동 바인딩이 없어 직접 파싱한다.
|
// admin 은 Spring Boot 가 아니라 yml 자동 바인딩이 없어 직접 파싱한다.
|
||||||
@@ -140,13 +140,18 @@ dependencies {
|
|||||||
|
|
||||||
implementation "org.apache.ibatis:ibatis-sqlmap:2.3.4.726"
|
implementation "org.apache.ibatis:ibatis-sqlmap:2.3.4.726"
|
||||||
implementation "org.jdom:jdom:1.1"
|
implementation "org.jdom:jdom:1.1"
|
||||||
implementation "com.google.code.gson:gson:2.3.1"
|
implementation "com.google.code.gson:gson:2.8.9"
|
||||||
implementation "javax.annotation:javax.annotation-api:1.2"
|
implementation "javax.annotation:javax.annotation-api:1.2"
|
||||||
implementation "net.sf.json-lib:json-lib-ext-spring:1.0.2"
|
implementation "net.sf.json-lib:json-lib-ext-spring:1.0.2"
|
||||||
implementation "taglibs:standard:1.1.2"
|
implementation "taglibs:standard:1.1.2"
|
||||||
implementation "javax.servlet:jstl:1.1.2"
|
implementation "javax.servlet:jstl:1.1.2"
|
||||||
implementation group: 'com.jcraft', name: 'jsch', version: '0.1.51'
|
implementation group: 'com.jcraft', name: 'jsch', version: '0.1.51'
|
||||||
|
|
||||||
|
// springapp-servlet.xml 의 CommonsMultipartResolver 가 사용.
|
||||||
|
// 기존엔 elink-online-common(api 'commons-fileupload:1.5')에서 전이적으로 받아왔으나,
|
||||||
|
// eapim-online 쪽에서 해당 의존성을 제거할 예정이라 직접 선언으로 전환.
|
||||||
|
implementation "commons-fileupload:commons-fileupload:1.6.0"
|
||||||
|
|
||||||
implementation "org.quartz-scheduler:quartz:${quartzVersion}"
|
implementation "org.quartz-scheduler:quartz:${quartzVersion}"
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.apache.poi/poi
|
// https://mvnrepository.com/artifact/org.apache.poi/poi
|
||||||
@@ -234,6 +239,11 @@ configurations.all {
|
|||||||
// 일부 transitive 가 끌어오는 xml-apis:1.0.b2 (2002, DOM L2) 는 이 클래스를 포함하지 않아
|
// 일부 transitive 가 끌어오는 xml-apis:1.0.b2 (2002, DOM L2) 는 이 클래스를 포함하지 않아
|
||||||
// xercesImpl 가 NoClassDefFoundError 를 일으킴 → 1.4.01 로 강제 통일.
|
// xercesImpl 가 NoClassDefFoundError 를 일으킴 → 1.4.01 로 강제 통일.
|
||||||
force 'xml-apis:xml-apis:1.4.01'
|
force 'xml-apis:xml-apis:1.4.01'
|
||||||
|
|
||||||
|
// logback 1.2.10 은 SLF4J 1.7 바인딩(StaticLoggerBinder)만 제공한다.
|
||||||
|
// transitive (Java-WebSocket:1.5.7 등) 가 slf4j-api 2.x 를 끌어올리면
|
||||||
|
// SLF4J 가 provider 를 못 찾아 NOP 로거로 폴백 → 콘솔/파일 로그가 전부 사라진다.
|
||||||
|
force 'org.slf4j:slf4j-api:1.7.36'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,11 +231,11 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
> Monitoring 프라퍼티 (환경정보 > EMS관리 > 모니터링 프라퍼티)
|
> Monitoring 프라퍼티 (환경정보 > EMS관리 > 모니터링 프라퍼티)
|
||||||
|
|
||||||
**계정관리**
|
**계정관리**
|
||||||
- `rms.DUAL_LOGIN_ENABLED` - 중복 로그인 허용 여부
|
|
||||||
- `rms.auto.logout.timeout` - 자동 로그아웃 시간(분)
|
|
||||||
- `djb.sms_auth.enabled` - 로그인시 2-factor 인증 적용여부
|
- `djb.sms_auth.enabled` - 로그인시 2-factor 인증 적용여부
|
||||||
- `djb.sms_auth.fixed_value` - sms_auth.mode=fixed일 경우 고정인증값
|
- `djb.sms_auth.fixed_value` - sms_auth.mode=fixed일 경우 고정인증값
|
||||||
- `djb.sms_auth.mode` - 2-factor 인증 모드 (real:실제, fixed:고정값-개발환경에서 사용)
|
- `djb.sms_auth.mode` - 2-factor 인증 모드 (real:실제, fixed:고정값-개발환경에서 사용)
|
||||||
|
- `rms.DUAL_LOGIN_ENABLED` - 중복 로그인 허용 여부
|
||||||
|
- `rms.auto.logout.timeout` - 자동 로그아웃 시간(분)
|
||||||
- `rms.password.combi.check` - 비밀번호 영문/숫자/특수문자 조합 종류수
|
- `rms.password.combi.check` - 비밀번호 영문/숫자/특수문자 조합 종류수
|
||||||
- `rms.password.fail.count` - 비밀번호 실패 허용 횟수(이 값 이상 실패시 계정 잠김)
|
- `rms.password.fail.count` - 비밀번호 실패 허용 횟수(이 값 이상 실패시 계정 잠김)
|
||||||
- `rms.password.history.count` - 이전 비밀번호 변경 불가 횟수
|
- `rms.password.history.count` - 이전 비밀번호 변경 불가 횟수
|
||||||
@@ -244,6 +244,10 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
- `rms.password.repeat.check` - 비밀번호 동일문자,연속문자 반복 불가 횟수
|
- `rms.password.repeat.check` - 비밀번호 동일문자,연속문자 반복 불가 횟수
|
||||||
- `rms.password.sms_auth.enabled` - 비밀번호 변경시 2-factor 인증 여부
|
- `rms.password.sms_auth.enabled` - 비밀번호 변경시 2-factor 인증 여부
|
||||||
|
|
||||||
|
**인터페이스 배포**
|
||||||
|
- `iomap.download.path` - 인터페이스.json 파일 다운로드 서버 경로
|
||||||
|
- `iomap.upload.path` - 인터페이스.json 파일 업로드 서버 경로
|
||||||
|
|
||||||
**UMS(공통) 연동**
|
**UMS(공통) 연동**
|
||||||
- `djb.ums.was_ip_address` - 관리자포탈 WAS IP 주소 (UMS 연동시 필요)
|
- `djb.ums.was_ip_address` - 관리자포탈 WAS IP 주소 (UMS 연동시 필요)
|
||||||
- `djb.ums.was_mac_address` - 관리자포탈 WAS MAC 주소 (UMS 연동 시 필요)
|
- `djb.ums.was_mac_address` - 관리자포탈 WAS MAC 주소 (UMS 연동 시 필요)
|
||||||
@@ -285,9 +289,8 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
## 스케쥴러
|
## 스케쥴러
|
||||||
|
|
||||||
**API 상태 모니터링**
|
**API 상태 모니터링**
|
||||||
- `ApiStatusMonitorJob11, ApiStatusMonitorJob21` - API 상태 변화를 모니터링한다(서버수 만큼 등록)
|
- `ApiStatusMonitorJob11, ApiStatusMonitorJob21` - API 상태 변화를 모니터링한다. API 상태 변화 발생시 직원에게 메신저 및 제휴사에게 웹훅을 발송한다.
|
||||||
- 실행간격(1분)이 짧아서 CLUSTERED는 적용을 못하고, 서버마다 번갈아 가면서 실행하도록 설정
|
- 실행간격(1분)이 짧아서 CLUSTERED는 적용을 못하고, 서버수만큼 등록하여 번갈아 가면서 실행하도록 설정
|
||||||
- API 상태 변화 발생시 직원에게 메신저 및 제휴사에게 웹훅을 발송한다.
|
|
||||||
- `api.status.error.range_minute` - API 장애 판단을 위한 로그 구간(분)
|
- `api.status.error.range_minute` - API 장애 판단을 위한 로그 구간(분)
|
||||||
- `api.status.error.rate` - API 장애판단을 위한 오류율 (ex : 10분동안 90% 이상 오류 발생시 장애로 판단)
|
- `api.status.error.rate` - API 장애판단을 위한 오류율 (ex : 10분동안 90% 이상 오류 발생시 장애로 판단)
|
||||||
- `api.status.delay.range_minute` - API 지연 상태 판단을 위한 로그 구간(분)
|
- `api.status.delay.range_minute` - API 지연 상태 판단을 위한 로그 구간(분)
|
||||||
@@ -304,8 +307,8 @@ DB 테이블 `PTL_PROPERTY` 에 그룹명 `'Portal'` 로 저장된다.
|
|||||||
- `APISTATSYEARLYJOB` - API 사용통계 년집계 (API_STATS_MONTH > API_STATS_YEAR)
|
- `APISTATSYEARLYJOB` - API 사용통계 년집계 (API_STATS_MONTH > API_STATS_YEAR)
|
||||||
|
|
||||||
**UMS발송**
|
**UMS발송**
|
||||||
- `UMSDISPATCHJOB11, UMSDISPATCHJOB21` - UMS를 발송한다 (서버수 만큼 등록)
|
- `UMSDISPATCHJOB11, UMSDISPATCHJOB21` - UMS를 발송한다.
|
||||||
- 실행간격(6초)이 짧아서 CLUSTERED는 적용을 못하고, 서버마다 번갈아 가면서 실행하도록 설정
|
- 실행간격(5초)이 짧아서 CLUSTERED는 적용을 못하고, 서버수만큼 등록하여 번갈아 가면서 실행하도록 설정
|
||||||
|
|
||||||
**게시글종료**
|
**게시글종료**
|
||||||
- `PortalInquiryClosingJob` - 답변완료 이후 일정기간 댓글이 없는 경우 게시글을 종료한다
|
- `PortalInquiryClosingJob` - 답변완료 이후 일정기간 댓글이 없는 경우 게시글을 종료한다
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import com.eactive.eai.rms.common.util.CommonConstants;
|
|||||||
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusiness;
|
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusiness;
|
||||||
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessId;
|
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessId;
|
||||||
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService;
|
import com.eactive.eai.rms.data.entity.onl.bzwkdstcd.UserBusinessService;
|
||||||
|
import com.eactive.eai.rms.data.entity.onl.transaction.TransactionTrackingFilter;
|
||||||
|
import com.eactive.eai.rms.data.entity.onl.transaction.TransactionTrackingFilterId;
|
||||||
|
import com.eactive.eai.rms.data.entity.onl.transaction.TransactionTrackingFilterService;
|
||||||
import com.eactive.eai.rms.data.entity.onl.unifbwk.UnifBwkTpService;
|
import com.eactive.eai.rms.data.entity.onl.unifbwk.UnifBwkTpService;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -40,6 +43,9 @@ public class BizManService extends BaseService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserBusinessService userBusinessService;
|
private UserBusinessService userBusinessService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TransactionTrackingFilterService transactionTrackingFilterService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserBizUIMapper userBizUIMapper;
|
private UserBizUIMapper userBizUIMapper;
|
||||||
|
|
||||||
@@ -102,6 +108,13 @@ public class BizManService extends BaseService {
|
|||||||
|
|
||||||
userBusinessService.deleteOrSave(userId, userBusinesses);
|
userBusinessService.deleteOrSave(userId, userBusinesses);
|
||||||
|
|
||||||
|
transactionTrackingFilterService.deleteAllByUserId(userId);
|
||||||
|
List<TransactionTrackingFilter> filters = userBusinesses.stream()
|
||||||
|
.map(ub -> new TransactionTrackingFilter(
|
||||||
|
new TransactionTrackingFilterId(userId, ub.getId().getEaiBzwkDstcd())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
transactionTrackingFilterService.saveAll(filters);
|
||||||
|
|
||||||
String userBusinessServiceLists = userBusinesses.stream()
|
String userBusinessServiceLists = userBusinesses.stream()
|
||||||
.map(e -> e.getId().getEaiBzwkDstcd())
|
.map(e -> e.getId().getEaiBzwkDstcd())
|
||||||
.collect(Collectors.joining(","));
|
.collect(Collectors.joining(","));
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class RequestWrapper extends HttpServletRequestWrapper {
|
|||||||
// src 속성 화이트리스트 필터링 - 허용된 프로토콜만 통과
|
// src 속성 화이트리스트 필터링 - 허용된 프로토콜만 통과
|
||||||
value = filterSrcAttribute(value);
|
value = filterSrcAttribute(value);
|
||||||
|
|
||||||
// 리치 텍스트 파라미터는 문자 변환을 건너뛰고 XSS 패턴 필터링만 적용
|
// 리치 텍스트 파라미터와 메시지 템플릿 저장 요청은 문자 변환 없이 XSS 패턴 필터링만 적용
|
||||||
if (!shouldSkipCharConvert()) {
|
if (!shouldSkipCharConvert()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
value = convertChars(value, sb);
|
value = convertChars(value, sb);
|
||||||
@@ -179,6 +179,15 @@ class RequestWrapper extends HttpServletRequestWrapper {
|
|||||||
if (param == null) {
|
if (param == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// 상세 화면은 .view로 열리지만 등록/수정 데이터는 .json으로 POST됨.
|
||||||
|
// 문자 발송에 사용되는 입력값이 HTML 엔티티로 저장되지 않도록 함.
|
||||||
|
if ("POST".equalsIgnoreCase(getMethod())
|
||||||
|
&& "/onl/apim/template/messageTemplateMan.json".equals(getServletPath())) {
|
||||||
|
String cmd = super.getParameter("cmd");
|
||||||
|
if ("INSERT".equals(cmd) || "UPDATE".equals(cmd)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (String skipParam : SKIP_CHAR_CONVERT_PARAMS) {
|
for (String skipParam : SKIP_CHAR_CONVERT_PARAMS) {
|
||||||
if (StringUtils.equalsIgnoreCase(param, skipParam)) {
|
if (StringUtils.equalsIgnoreCase(param, skipParam)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ public class MainController implements InterceptorSkipController {
|
|||||||
// 휴대폰번호 없는 사용자
|
// 휴대폰번호 없는 사용자
|
||||||
return LoginResponseDto.builder()
|
return LoginResponseDto.builder()
|
||||||
.success(false)
|
.success(false)
|
||||||
.errorMessage("휴대폰번호가 등록되어 있지 않아 로그인할 수 없습니다. 관리자에게 문의하세요.")
|
.errorMessage("NO_PHONE_NUMBER")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ public class MainController implements InterceptorSkipController {
|
|||||||
if (!sent) {
|
if (!sent) {
|
||||||
return LoginResponseDto.builder()
|
return LoginResponseDto.builder()
|
||||||
.success(false)
|
.success(false)
|
||||||
.errorMessage("인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.")
|
.errorMessage("FAIL_SEND_AUTH")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi;
|
|||||||
import com.eactive.eai.data.jpa.AbstractDataService;
|
import com.eactive.eai.data.jpa.AbstractDataService;
|
||||||
import com.eactive.eai.rms.onl.apim.apigroup.ui.ApiGroupUISearch;
|
import com.eactive.eai.rms.onl.apim.apigroup.ui.ApiGroupUISearch;
|
||||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||||
|
import com.querydsl.core.Tuple;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -47,6 +50,24 @@ public class ApiGroupService extends AbstractDataService<ApiGroup, String, ApiGr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 검색 콤보용 그룹 ID/명칭. 게시 여부와 관계없이 모든 그룹을 제공한다. */
|
||||||
|
public List<Map<String, String>> findGroupOptions() {
|
||||||
|
QApiGroup group = QApiGroup.apiGroup;
|
||||||
|
List<Tuple> rows = getJPAQueryFactory()
|
||||||
|
.select(group.id, group.groupName)
|
||||||
|
.from(group)
|
||||||
|
.orderBy(group.displayOrder.asc().nullsLast(), group.groupName.asc(), group.id.asc())
|
||||||
|
.fetch();
|
||||||
|
List<Map<String, String>> options = new ArrayList<>();
|
||||||
|
for (Tuple row : rows) {
|
||||||
|
Map<String, String> option = new HashMap<>();
|
||||||
|
option.put("id", row.get(group.id));
|
||||||
|
option.put("groupName", StringUtils.defaultIfBlank(row.get(group.groupName), "그룹명 없음"));
|
||||||
|
options.add(option);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteApiGroupApiByApiId(String apiId) {
|
public void deleteApiGroupApiByApiId(String apiId) {
|
||||||
List<ApiGroup> apiGroups = repository.findAll();
|
List<ApiGroup> apiGroups = repository.findAll();
|
||||||
for (ApiGroup apiGroup : apiGroups) {
|
for (ApiGroup apiGroup : apiGroups) {
|
||||||
@@ -77,6 +98,28 @@ public class ApiGroupService extends AbstractDataService<ApiGroup, String, ApiGr
|
|||||||
.fetch();
|
.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 목록 툴팁용 게시 그룹명. Oracle IN 절 제한에 맞춰 페이지의 API들을 일괄 조회한다. */
|
||||||
|
public Map<String, List<String>> findPublishedGroupNamesByApiIds(List<String> apiIds) {
|
||||||
|
Map<String, List<String>> groupNames = new HashMap<>();
|
||||||
|
QApiGroup group = QApiGroup.apiGroup;
|
||||||
|
QApiGroupApi membership = QApiGroupApi.apiGroupApi;
|
||||||
|
for (int from = 0; from < apiIds.size(); from += 1000) {
|
||||||
|
List<String> chunk = apiIds.subList(from, Math.min(from + 1000, apiIds.size()));
|
||||||
|
List<Tuple> rows = getJPAQueryFactory()
|
||||||
|
.select(membership.id.apiId, group.groupName)
|
||||||
|
.from(membership)
|
||||||
|
.join(membership.apiGroup, group)
|
||||||
|
.where(membership.id.apiId.in(chunk), group.displayYn.eq("1"))
|
||||||
|
.orderBy(group.displayOrder.asc().nullsLast(), group.groupName.asc(), group.id.asc())
|
||||||
|
.fetch();
|
||||||
|
for (Tuple row : rows) {
|
||||||
|
groupNames.computeIfAbsent(row.get(membership.id.apiId), key -> new ArrayList<>())
|
||||||
|
.add(StringUtils.defaultIfBlank(row.get(group.groupName), "그룹명 없음"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupNames;
|
||||||
|
}
|
||||||
|
|
||||||
/** API 를 지정한 그룹 목록에만 소속시킨다(기존 소속 제거 후 재설정). */
|
/** API 를 지정한 그룹 목록에만 소속시킨다(기존 소속 제거 후 재설정). */
|
||||||
public void setApiGroupsForApi(String apiId, List<String> groupIds) {
|
public void setApiGroupsForApi(String apiId, List<String> groupIds) {
|
||||||
deleteApiGroupApiByApiId(apiId);
|
deleteApiGroupApiByApiId(apiId);
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.eactive.eai.rms.data.entity.onl.apim.apispec;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
|
||||||
|
import com.eactive.eai.rms.data.EMSDataSource;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
/** EAI 목록과 스키마가 다른 EMS 스펙의 ID/게시 여부만 조회한다. */
|
||||||
|
@EMSDataSource
|
||||||
|
public interface ApiSpecStatusRepository extends Repository<ApiSpecInfo, String> {
|
||||||
|
|
||||||
|
@Query("SELECT a.apiId FROM ApiSpecInfo a WHERE "
|
||||||
|
+ "(:status = 'PUBLISHED' AND a.displayYn = 'Y') OR "
|
||||||
|
+ "(:status = 'REGISTERED' AND (a.displayYn IS NULL OR a.displayYn <> 'Y'))")
|
||||||
|
List<String> findApiIdsByStatus(@Param("status") String status);
|
||||||
|
|
||||||
|
@Query("SELECT a.apiId AS apiId, a.displayYn AS displayYn FROM ApiSpecInfo a WHERE a.apiId IN :apiIds")
|
||||||
|
List<SpecStatus> findStatusesByApiIds(@Param("apiIds") Collection<String> apiIds);
|
||||||
|
|
||||||
|
interface SpecStatus {
|
||||||
|
String getApiId();
|
||||||
|
String getDisplayYn();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.eactive.eai.rms.data.entity.onl.eaimsg;
|
package com.eactive.eai.rms.data.entity.onl.eaimsg;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository;
|
import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository;
|
||||||
|
import com.eactive.eai.rms.data.entity.onl.apim.apispec.ApiSpecStatusRepository;
|
||||||
import com.eactive.eai.data.entity.onl.adapter.QAdapter;
|
import com.eactive.eai.data.entity.onl.adapter.QAdapter;
|
||||||
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroup;
|
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroup;
|
||||||
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi;
|
import com.eactive.eai.data.entity.onl.apim.apigroup.QApiGroupApi;
|
||||||
@@ -51,6 +53,7 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
|
|||||||
|
|
||||||
/** EMSAPP(PTL_API_SPEC_INFO) 조회용. 다른 스키마라 EAI 메시지 쿼리에 조인할 수 없다 */
|
/** EMSAPP(PTL_API_SPEC_INFO) 조회용. 다른 스키마라 EAI 메시지 쿼리에 조인할 수 없다 */
|
||||||
private final ApiSpecInfoRepository apiSpecInfoRepository;
|
private final ApiSpecInfoRepository apiSpecInfoRepository;
|
||||||
|
private final ApiSpecStatusRepository apiSpecStatusRepository;
|
||||||
|
|
||||||
public Page<Tuple> selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable, String sortname, String sortorder) {
|
public Page<Tuple> selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable, String sortname, String sortorder) {
|
||||||
|
|
||||||
@@ -201,6 +204,32 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
|
|||||||
predicate
|
predicate
|
||||||
.and(qStdInfo.apifullpath.contains(eaiMessageUISearch.getSearchApiFullPath()));
|
.and(qStdInfo.apifullpath.contains(eaiMessageUISearch.getSearchApiFullPath()));
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchInOutType())) {
|
||||||
|
predicate.and(qeaiMessageEntity.eaisvcname.endsWith(eaiMessageUISearch.getSearchInOutType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String specStatus = eaiMessageUISearch.getSearchSpecStatus();
|
||||||
|
if ("UNREGISTERED".equals(specStatus)) {
|
||||||
|
predicate.and(apiSpecRegisteredExpression(qeaiMessageEntity.eaisvcname).not());
|
||||||
|
} else if ("REGISTERED".equals(specStatus) || "PUBLISHED".equals(specStatus)) {
|
||||||
|
predicate.and(apiIdInExpression(qeaiMessageEntity.eaisvcname,
|
||||||
|
apiSpecStatusRepository.findApiIdsByStatus(specStatus)));
|
||||||
|
}
|
||||||
|
|
||||||
|
String apiGroupId = eaiMessageUISearch.getSearchApiGroupId();
|
||||||
|
if (StringUtils.isNotBlank(apiGroupId)) {
|
||||||
|
QApiGroupApi membership = QApiGroupApi.apiGroupApi;
|
||||||
|
QApiGroup group = QApiGroup.apiGroup;
|
||||||
|
JPQLQuery<Integer> groupQuery = JPAExpressions.selectOne()
|
||||||
|
.from(membership)
|
||||||
|
.join(membership.apiGroup, group)
|
||||||
|
.where(membership.id.apiId.eq(qeaiMessageEntity.eaisvcname));
|
||||||
|
if ("UNREGISTERED".equals(apiGroupId)) {
|
||||||
|
predicate.and(groupQuery.notExists());
|
||||||
|
} else {
|
||||||
|
predicate.and(groupQuery.where(group.id.eq(apiGroupId)).exists());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchStatusCode())) {
|
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchStatusCode())) {
|
||||||
String statusCode = eaiMessageUISearch.getSearchStatusCode();
|
String statusCode = eaiMessageUISearch.getSearchStatusCode();
|
||||||
@@ -230,7 +259,10 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
|
|||||||
* API ID 를 EMS 커넥션에서 따로 읽어 IN 절로 넘긴다.</p>
|
* API ID 를 EMS 커넥션에서 따로 읽어 IN 절로 넘긴다.</p>
|
||||||
*/
|
*/
|
||||||
private BooleanExpression apiSpecRegisteredExpression(StringPath eaiSvcNamePath) {
|
private BooleanExpression apiSpecRegisteredExpression(StringPath eaiSvcNamePath) {
|
||||||
List<String> specApiIds = apiSpecInfoRepository.findAllApiIds();
|
return apiIdInExpression(eaiSvcNamePath, apiSpecInfoRepository.findAllApiIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BooleanExpression apiIdInExpression(StringPath eaiSvcNamePath, List<String> specApiIds) {
|
||||||
if (specApiIds.isEmpty()) {
|
if (specApiIds.isEmpty()) {
|
||||||
// in(빈 리스트) 는 JPQL 이 깨지므로 항상 거짓인 조건으로 대체한다
|
// in(빈 리스트) 는 JPQL 이 깨지므로 항상 거짓인 조건으로 대체한다
|
||||||
return Expressions.ONE.eq(Expressions.TWO);
|
return Expressions.ONE.eq(Expressions.TWO);
|
||||||
@@ -245,6 +277,18 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
|
|||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 현재 페이지의 스펙 상태를 일괄 조회한다. Oracle IN 절은 1,000개씩 나눈다. */
|
||||||
|
public Map<String, String> findApiSpecStatuses(List<String> apiIds) {
|
||||||
|
Map<String, String> statuses = new HashMap<>();
|
||||||
|
for (int from = 0; from < apiIds.size(); from += ORACLE_IN_LIMIT) {
|
||||||
|
int to = Math.min(from + ORACLE_IN_LIMIT, apiIds.size());
|
||||||
|
for (ApiSpecStatusRepository.SpecStatus spec : apiSpecStatusRepository.findStatusesByApiIds(apiIds.subList(from, to))) {
|
||||||
|
statuses.put(spec.getApiId(), "Y".equals(spec.getDisplayYn()) ? "PUBLISHED" : "REGISTERED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statuses;
|
||||||
|
}
|
||||||
|
|
||||||
/** 노출 중인(display_yn='1') API 그룹에 편성된 API ID. API_GROUP 은 EAI 메시지와 같은 스키마다 */
|
/** 노출 중인(display_yn='1') API 그룹에 편성된 API ID. API_GROUP 은 EAI 메시지와 같은 스키마다 */
|
||||||
private JPQLQuery<String> displayedGroupApiIdSubQuery() {
|
private JPQLQuery<String> displayedGroupApiIdSubQuery() {
|
||||||
QApiGroupApi qApiGroupApi = QApiGroupApi.apiGroupApi;
|
QApiGroupApi qApiGroupApi = QApiGroupApi.apiGroupApi;
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ public class EAIMessageUISearch {
|
|||||||
private String searchRefKey;
|
private String searchRefKey;
|
||||||
|
|
||||||
private String searchApiFullPath;
|
private String searchApiFullPath;
|
||||||
|
/** API ID 마지막 자리 (1: 당발, 2: 타발) */
|
||||||
|
private String searchInOutType;
|
||||||
|
/** UNREGISTERED: 미등록, REGISTERED: 등록(비게시), PUBLISHED: 게시 */
|
||||||
|
private String searchSpecStatus;
|
||||||
|
/** 그룹 ID. UNREGISTERED이면 소속 그룹이 없는 API */
|
||||||
|
private String searchApiGroupId;
|
||||||
|
|
||||||
/** API_STATUS.STATUS_CODE (E:장애, D:지연, C:점검). "N" 이면 정상(코드 없음)만 조회 */
|
/** API_STATUS.STATUS_CODE (E:장애, D:지연, C:점검). "N" 이면 정상(코드 없음)만 조회 */
|
||||||
private String searchStatusCode;
|
private String searchStatusCode;
|
||||||
|
|||||||
+3
-3
@@ -336,12 +336,12 @@ public class ApiStatsMinuteService
|
|||||||
|
|
||||||
if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
|
if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
|
||||||
endDateTime = LocalDateTime.parse(search.getSearchEndDateTime(), DATE_TIME_FORMATTER);
|
endDateTime = LocalDateTime.parse(search.getSearchEndDateTime(), DATE_TIME_FORMATTER);
|
||||||
// 1시간(60분) 초과 시 시작시간 기준 1시간 후로 강제 설정
|
// 1시간(60분) 초과 시 시작시간 기준 1시간치(정시~59분, statTime 조건이 양끝 포함이므로 -1분)로 강제 설정
|
||||||
if (java.time.Duration.between(startDateTime, endDateTime).toMinutes() > MAX_MINUTES) {
|
if (java.time.Duration.between(startDateTime, endDateTime).toMinutes() > MAX_MINUTES) {
|
||||||
endDateTime = startDateTime.plusMinutes(MAX_MINUTES);
|
endDateTime = startDateTime.plusMinutes(MAX_MINUTES - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
endDateTime = startDateTime.plusMinutes(MAX_MINUTES);
|
endDateTime = startDateTime.plusMinutes(MAX_MINUTES - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
search.setParsedStartDateTime(startDateTime);
|
search.setParsedStartDateTime(startDateTime);
|
||||||
|
|||||||
+8
@@ -20,4 +20,12 @@ public class TransactionTrackingFilterService extends
|
|||||||
.fetch();
|
.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteAllByUserId(String userId) {
|
||||||
|
repository.deleteAll(findAllByUserId(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAll(List<TransactionTrackingFilter> filters) {
|
||||||
|
repository.saveAll(filters);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package com.eactive.eai.rms.ext.djb.ums.service;
|
package com.eactive.eai.rms.ext.djb.ums.service;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -40,11 +44,9 @@ public class UmsService {
|
|||||||
private static final Gson gson = new Gson();
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
private static final String SYS_DVCD = "API"; //시스템코드
|
private static final String SYS_DVCD = "API"; //시스템코드
|
||||||
private static final String BZWK_DVCD = "99"; //업무구분코드
|
|
||||||
private static final String BZWK_CD = "999"; //업무코드
|
|
||||||
private static final String BZWK_DCLS_DVCD = "9019"; //세부구분코드
|
private static final String BZWK_DCLS_DVCD = "9019"; //세부구분코드
|
||||||
private static final String SUB_BZWK_CD = "999"; //서브업무코드 TODO: 변경할것
|
private static final String BZWK_CD = "EBK"; //업무코드
|
||||||
private static final String TEAM_DVCD = "99"; //팀구분코드 TODO: 변경해야함
|
private static final String SUB_BZWK_CD = "EBG"; //서브업무코드
|
||||||
|
|
||||||
|
|
||||||
private final MonitoringPropertyService propertyService;
|
private final MonitoringPropertyService propertyService;
|
||||||
@@ -60,13 +62,21 @@ public class UmsService {
|
|||||||
throw new IllegalArgumentException("messageRequest is null");
|
throw new IllegalArgumentException("messageRequest is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.httpConnection(messageRequest);
|
UmsResponse response = this.httpConnection(messageRequest);
|
||||||
|
|
||||||
return true;
|
// HTTP 응답코드가 2xx 일 때만 SENT, 나머지는 FAILED
|
||||||
|
boolean success = response.getHttpStatus() >= 200 && response.getHttpStatus() < 300;
|
||||||
|
|
||||||
|
messageRequest.setRequestStatus(success ? "SENT" : "FAILED");
|
||||||
|
messageRequest.setSentDate(LocalDateTime.now());
|
||||||
|
messageRequest.setResponseData(response.getResponseData());
|
||||||
|
messageRequestService.save(messageRequest);
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void httpConnection(MessageRequest messageRequest) throws Exception {
|
private UmsResponse httpConnection(MessageRequest messageRequest) throws Exception {
|
||||||
HttpURLConnection conn = null;
|
HttpURLConnection conn = null;
|
||||||
BufferedReader br = null;
|
BufferedReader br = null;
|
||||||
log.debug("<<<HTTP Connection>>>:" + messageRequest.toString());
|
log.debug("<<<HTTP Connection>>>:" + messageRequest.toString());
|
||||||
@@ -99,18 +109,18 @@ public class UmsService {
|
|||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int responseCode = conn.getResponseCode();
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("getContentType();" + conn.getContentType()); // 응답 콘텐츠 유형 구하기
|
log.debug("getContentType();" + conn.getContentType()); // 응답 콘텐츠 유형 구하기
|
||||||
log.debug("getResponsecode();" + conn.getResponseCode()); // 응답 코드 구하기
|
log.debug("getResponsecode();" + responseCode); // 응답 코드 구하기
|
||||||
log.debug("getResponseMessage():" + conn.getResponseMessage()); // 응답 메세지 구하기
|
log.debug("getResponseMessage():" + conn.getResponseMessage()); // 응답 메세지 구하기
|
||||||
}
|
}
|
||||||
|
|
||||||
// http 요청 후 응답 받은 데이타를 버퍼에 쌓는다
|
// http 요청 후 응답 받은 데이타를 버퍼에 쌓는다
|
||||||
if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
|
InputStream is = (responseCode >= 200 && responseCode < 300) ? conn.getInputStream() : conn.getErrorStream();
|
||||||
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
|
if (is != null) {
|
||||||
} else {
|
br = new BufferedReader(new InputStreamReader(is, charset));
|
||||||
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), charset));
|
|
||||||
}
|
|
||||||
|
|
||||||
String inputLine;
|
String inputLine;
|
||||||
sb = new StringBuffer();
|
sb = new StringBuffer();
|
||||||
@@ -119,6 +129,11 @@ public class UmsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
responseData = sb.toString();
|
responseData = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new UmsResponse(responseCode, responseData);
|
||||||
} finally {
|
} finally {
|
||||||
// http 요청 및 응답 완료 후 BufferedReader를 닫는다
|
// http 요청 및 응답 완료 후 BufferedReader를 닫는다
|
||||||
if (br != null) {
|
if (br != null) {
|
||||||
@@ -130,7 +145,64 @@ public class UmsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** UMS HTTP 호출 결과(응답코드 + 응답본문) 보관용. */
|
||||||
|
private static class UmsResponse {
|
||||||
|
private final int httpStatus;
|
||||||
|
private final String responseData;
|
||||||
|
|
||||||
|
UmsResponse(int httpStatus, String responseData) {
|
||||||
|
this.httpStatus = httpStatus;
|
||||||
|
this.responseData = responseData;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHttpStatus() {
|
||||||
|
return httpStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getResponseData() {
|
||||||
|
return responseData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 표준 헤더의 최초전문송신IP(frst_trnm_ipad)용 - 프로퍼티 설정값 대신 현재 서버(WAS)의 실제 IP를 조회한다.
|
||||||
|
* 조회 실패 시에는 기존처럼 djb.ums.was_ip_address 프로퍼티 값으로 대체한다.
|
||||||
|
*/
|
||||||
|
private String getCurrentServerIp() {
|
||||||
|
try {
|
||||||
|
return InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("getCurrentServerIp error", e);
|
||||||
|
return propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 표준 헤더의 최초전문송신MAC(frst_trnm_mac)용 - 프로퍼티 설정값 대신 현재 서버(WAS)의 실제 MAC 주소를 조회한다.
|
||||||
|
* 조회 실패(네트워크 인터페이스를 찾지 못하는 경우 포함) 시에는 기존처럼 djb.ums.was_mac_address 프로퍼티 값으로 대체한다.
|
||||||
|
*/
|
||||||
|
private String getCurrentServerMac() {
|
||||||
|
try {
|
||||||
|
NetworkInterface ni = NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
|
||||||
|
byte[] mac = (ni != null) ? ni.getHardwareAddress() : null;
|
||||||
|
if (mac == null) {
|
||||||
|
return propertyService.getPropertyValue("Monitoring", "djb.ums.was_mac_address", "");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < mac.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append(":");
|
||||||
|
}
|
||||||
|
sb.append(String.format("%02X", mac[i]));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("getCurrentServerMac error", e);
|
||||||
|
return propertyService.getPropertyValue("Monitoring", "djb.ums.was_mac_address", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String makeBodyData(MessageRequest messageRequest) {
|
private String makeBodyData(MessageRequest messageRequest) {
|
||||||
// 리턴값
|
// 리턴값
|
||||||
@@ -147,9 +219,9 @@ public class UmsService {
|
|||||||
stdHeadVO.setStnd_mesg_ver("R10");
|
stdHeadVO.setStnd_mesg_ver("R10");
|
||||||
stdHeadVO.setOrtr_guid(jsonData[2]);
|
stdHeadVO.setOrtr_guid(jsonData[2]);
|
||||||
stdHeadVO.setSect_ecrpt_yn("N");
|
stdHeadVO.setSect_ecrpt_yn("N");
|
||||||
stdHeadVO.setFrst_trnm_ipad(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", ""));
|
stdHeadVO.setFrst_trnm_ipad(getCurrentServerIp());
|
||||||
stdHeadVO.setFrst_trnm_ipv4_addr(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", ""));
|
stdHeadVO.setFrst_trnm_ipv4_addr(getCurrentServerIp());
|
||||||
stdHeadVO.setFrst_trnm_mac(propertyService.getPropertyValue("Monitoring", "djb.ums.was_mac_address", ""));
|
stdHeadVO.setFrst_trnm_mac(getCurrentServerMac());
|
||||||
stdHeadVO.setFrst_mesg_dman_dt(jsonData[0]);
|
stdHeadVO.setFrst_mesg_dman_dt(jsonData[0]);
|
||||||
stdHeadVO.setFrst_mesg_dman_time(jsonData[1]);
|
stdHeadVO.setFrst_mesg_dman_time(jsonData[1]);
|
||||||
stdHeadVO.setFrst_trnm_sys_dvcd(SYS_DVCD);
|
stdHeadVO.setFrst_trnm_sys_dvcd(SYS_DVCD);
|
||||||
@@ -161,7 +233,7 @@ public class UmsService {
|
|||||||
stdHeadVO.setChnl_tycd("EAI"); // 채널유형코드
|
stdHeadVO.setChnl_tycd("EAI"); // 채널유형코드
|
||||||
stdHeadVO.setHmab_dvcd("1"); // 내외구분코드 1:내부 2:외부(타발)
|
stdHeadVO.setHmab_dvcd("1"); // 내외구분코드 1:내부 2:외부(타발)
|
||||||
stdHeadVO.setIf_id(messageRequest.getEaiInterfaceId()); // 인터페이스ID
|
stdHeadVO.setIf_id(messageRequest.getEaiInterfaceId()); // 인터페이스ID
|
||||||
stdHeadVO.setTx_id(messageRequest.getServiceId()); // 거래ID
|
stdHeadVO.setTx_id(messageRequest.getEaiTxId()); // 거래ID
|
||||||
|
|
||||||
if ("MESSENGER".equals(messageRequest.getMessageType()) ) {
|
if ("MESSENGER".equals(messageRequest.getMessageType()) ) {
|
||||||
|
|
||||||
@@ -205,7 +277,7 @@ public class UmsService {
|
|||||||
// Sms 데이터
|
// Sms 데이터
|
||||||
SmsDataVO dataVO = new SmsDataVO();
|
SmsDataVO dataVO = new SmsDataVO();
|
||||||
dataVO.setSnd_dman_dt(jsonData[0]);
|
dataVO.setSnd_dman_dt(jsonData[0]);
|
||||||
dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest.getMessageType()));
|
dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest));
|
||||||
dataVO.setSnd_dvcd("EAI");
|
dataVO.setSnd_dvcd("EAI");
|
||||||
dataVO.setSms_msg_titl(StringUtil.chunkString(messageRequest.getSubject(), 40, false)); // MS949 40byte 초과시 절단
|
dataVO.setSms_msg_titl(StringUtil.chunkString(messageRequest.getSubject(), 40, false)); // MS949 40byte 초과시 절단
|
||||||
dataVO.setSms_msg_ctnt(messageRequest.getMessage());
|
dataVO.setSms_msg_ctnt(messageRequest.getMessage());
|
||||||
@@ -215,19 +287,21 @@ public class UmsService {
|
|||||||
dataVO.setSnd_emp_dprm_cd("132"); //요청부서코드
|
dataVO.setSnd_emp_dprm_cd("132"); //요청부서코드
|
||||||
dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS
|
dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS
|
||||||
dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
|
dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
|
||||||
//ADMIN으로 시작하면 제주은행 임직원, 나머지는 제주은행 고객용
|
//ADMIN으로 시작하면 제주은행 내부용, 나머지는 제주은행 고객용
|
||||||
if (messageRequest.getMessageCode().name().startsWith("ADMIN")) {
|
if (messageRequest.getMessageCode().name().startsWith("ADMIN")) {
|
||||||
|
dataVO.setSms_trnm_bzwk_dvcd("99"); //업무구분코드
|
||||||
|
dataVO.setSms_bzwk_dcls_dvcd(BZWK_DCLS_DVCD); //업무세분코드
|
||||||
dataVO.setAlmtk_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key.intra", "")); // 제주은행 임직원 플러스친구아이디
|
dataVO.setAlmtk_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key.intra", "")); // 제주은행 임직원 플러스친구아이디
|
||||||
} else {
|
} else {
|
||||||
|
dataVO.setSms_trnm_bzwk_dvcd("95"); //업무구분코드
|
||||||
|
dataVO.setSms_bzwk_dcls_dvcd(BZWK_DCLS_DVCD); //업무세분코드
|
||||||
dataVO.setAlmtk_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key.public", "")); // 제주은행 플러스친구아이디
|
dataVO.setAlmtk_snd_prfl_key(propertyService.getPropertyValue("Monitoring", "djb.ums.sms.almtk_snd_prtl_key.public", "")); // 제주은행 플러스친구아이디
|
||||||
}
|
}
|
||||||
dataVO.setAlmtk_tmplt_cd(BZWK_DVCD + TEAM_DVCD + BZWK_DCLS_DVCD + "001"); // 템플릿코드
|
dataVO.setAlmtk_tmplt_cd(messageRequest.getServiceId()); // 템플릿코드
|
||||||
dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부
|
dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부
|
||||||
dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML
|
dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML
|
||||||
|
|
||||||
dataVO.setSms_trnm_bzwk_dvcd(BZWK_DVCD); //업무구분코드
|
dataVO.setSms_sys_dvcd("85"); //시스템코드
|
||||||
dataVO.setSms_bzwk_dcls_dvcd(BZWK_DCLS_DVCD); //업무세분코드
|
|
||||||
dataVO.setSms_sys_dvcd("83"); //시스템코드
|
|
||||||
dataVO.setTx_dt(jsonData[0]); //거래일자
|
dataVO.setTx_dt(jsonData[0]); //거래일자
|
||||||
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
|
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
|
||||||
|
|
||||||
@@ -251,7 +325,7 @@ public class UmsService {
|
|||||||
EmailDataVO dataVO = new EmailDataVO();
|
EmailDataVO dataVO = new EmailDataVO();
|
||||||
dataVO.setUms_evnt_id(propertyService.getPropertyValue("Monitoring", "djb.ums.email.ums_evnt_id", "")); // UMS이벤트ID(필수)
|
dataVO.setUms_evnt_id(propertyService.getPropertyValue("Monitoring", "djb.ums.email.ums_evnt_id", "")); // UMS이벤트ID(필수)
|
||||||
dataVO.setSnd_dman_dt(jsonData[0]);
|
dataVO.setSnd_dman_dt(jsonData[0]);
|
||||||
dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest.getMessageType()));
|
dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest));
|
||||||
dataVO.setDman_seqno(1); // 발송요청순번
|
dataVO.setDman_seqno(1); // 발송요청순번
|
||||||
dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목
|
dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목
|
||||||
dataVO.setCstno(propertyService.getPropertyValue("Monitoring", "djb.ums.email.cstno", "")); // 고객번호(필수)
|
dataVO.setCstno(propertyService.getPropertyValue("Monitoring", "djb.ums.email.cstno", "")); // 고객번호(필수)
|
||||||
@@ -263,7 +337,7 @@ public class UmsService {
|
|||||||
dataVO.setSnd_emp_dprm_cd("132"); // 발신자부서코드(필수)
|
dataVO.setSnd_emp_dprm_cd("132"); // 발신자부서코드(필수)
|
||||||
dataVO.setBzwk_cd(BZWK_CD); // 업무코드
|
dataVO.setBzwk_cd(BZWK_CD); // 업무코드
|
||||||
dataVO.setSub_bzwk_cd(SUB_BZWK_CD); // 서브업무코드
|
dataVO.setSub_bzwk_cd(SUB_BZWK_CD); // 서브업무코드
|
||||||
dataVO.setSys_dvcd(SYS_DVCD); // 시스템구분코드
|
dataVO.setSys_dvcd("85"); // 시스템구분코드
|
||||||
dataVO.setTx_dt(jsonData[0]); // 거래일자
|
dataVO.setTx_dt(jsonData[0]); // 거래일자
|
||||||
dataVO.setTx_tktm(jsonData[1].substring(0,6)); // 거래시간
|
dataVO.setTx_tktm(jsonData[1].substring(0,6)); // 거래시간
|
||||||
|
|
||||||
@@ -357,20 +431,24 @@ public class UmsService {
|
|||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSndDmanId(String messageType) {
|
private String getSndDmanId(MessageRequest messageRequest) {
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||||
String yyyymmdd = sdf.format(new Date());
|
String yyyymmdd = sdf.format(new Date());
|
||||||
|
|
||||||
String uuid = "";
|
String uuid = "";
|
||||||
|
|
||||||
if ("EMAIL".equals(messageType)) {
|
if ("EMAIL".equals(messageRequest.getMessageType())) {
|
||||||
uuid = SYS_DVCD + BZWK_CD + SUB_BZWK_CD + yyyymmdd + "O" + "E" ;
|
uuid = SYS_DVCD + BZWK_CD + SUB_BZWK_CD + yyyymmdd + "O" + "E" ;
|
||||||
|
|
||||||
long seq = messageRequestService.countEmailRequestsOnDate(LocalDate.now()) + 1;
|
long seq = messageRequestService.countEmailRequestsOnDate(LocalDate.now()) + 1;
|
||||||
uuid += String.format("%013d", seq);
|
uuid += String.format("%013d", seq);
|
||||||
|
|
||||||
} else if ("SMS".equals(messageType)) { //kakao 알림톡
|
} else if ("SMS".equals(messageRequest.getMessageType())) { //kakao 알림톡
|
||||||
uuid = SYS_DVCD + BZWK_DVCD + BZWK_DCLS_DVCD + yyyymmdd + "O" + "K";
|
if (messageRequest.getMessageCode().name().startsWith("ADMIN")) {
|
||||||
|
uuid = SYS_DVCD + "99" + BZWK_DCLS_DVCD + yyyymmdd + "O" + "K"; //내부용
|
||||||
|
} else {
|
||||||
|
uuid = SYS_DVCD + "95" + BZWK_DCLS_DVCD + yyyymmdd + "O" + "K"; //대고객
|
||||||
|
}
|
||||||
|
|
||||||
long seq = messageRequestService.countSmsKakaoRequestsOnDate(LocalDate.now()) + 1;
|
long seq = messageRequestService.countSmsKakaoRequestsOnDate(LocalDate.now()) + 1;
|
||||||
uuid += String.format("%013d", seq);
|
uuid += String.format("%013d", seq);
|
||||||
|
|||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
package com.eactive.eai.rms.onl.apim.approval.credential;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
|
||||||
|
import com.eactive.apim.portal.apispec.service.ApiSpecInfoService;
|
||||||
|
import com.eactive.apim.portal.app.entity.Credential;
|
||||||
|
import com.eactive.eai.data.entity.onl.authserver.ClientEntity;
|
||||||
|
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
|
||||||
|
import com.eactive.eai.rms.onl.transaction.apim.ApiSpecManService;
|
||||||
|
import com.eactive.eai.rms.onl.transaction.apim.DjbApiSpecAutoGenService;
|
||||||
|
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIGW 인증 클라이언트(TSEAIAU01) → 개발자포탈(ptl_credential) 반영, 포털 쪽 쓰기 전담.
|
||||||
|
*
|
||||||
|
* {@link com.eactive.eai.rms.onl.apim.approval.app.GwClientSyncService}(포털→GW 방향)의 반대 방향 대응물.
|
||||||
|
* 트랜잭션 매니저를 EMS(MONITORING 스키마 고정)로 명시해, 호출부(APIGW 트랜잭션)의
|
||||||
|
* {@code DataSourceContextHolder} 를 건드리지 않는다.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PortalCredentialSyncService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PortalCredentialSyncService.class);
|
||||||
|
|
||||||
|
private static final String DEFAULT_APP_DESCRIPTION = "APIGW에서 반영된 인증정보";
|
||||||
|
|
||||||
|
private final CredentialService credentialService;
|
||||||
|
private final ApiSpecInfoService apiSpecInfoService;
|
||||||
|
private final ApiSpecManService apiSpecManService;
|
||||||
|
private final DjbApiSpecAutoGenService autoGenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GW 클라이언트 정보를 포털 Credential 로 반영한다(없으면 신규, 있으면 갱신).
|
||||||
|
*
|
||||||
|
* @param gwClient APIGW 스키마에서 조회한 클라이언트(orgid 는 공백이 아니어야 한다 - 호출부 사전 검증)
|
||||||
|
* @return 반영 결과
|
||||||
|
*/
|
||||||
|
public Outcome apply(ClientEntity gwClient) {
|
||||||
|
String clientId = gwClient.getClientid();
|
||||||
|
String orgId = gwClient.getOrgid();
|
||||||
|
|
||||||
|
Credential existing = credentialService.findByClientidAndOrgid(clientId, orgId);
|
||||||
|
boolean isNew = existing == null;
|
||||||
|
Credential credential = isNew ? new Credential() : existing;
|
||||||
|
if (isNew) {
|
||||||
|
credential.setClientid(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyGwFields(gwClient, credential);
|
||||||
|
|
||||||
|
if (isNew && StringUtils.isBlank(credential.getAppDescription())) {
|
||||||
|
credential.setAppDescription(DEFAULT_APP_DESCRIPTION);
|
||||||
|
}
|
||||||
|
// appIconFileId / server 는 포털 고유 필드라 GW 에 대응값이 없다 - 기존 값 그대로 유지(신규면 null)
|
||||||
|
|
||||||
|
Outcome outcome = new Outcome(isNew);
|
||||||
|
credential.setApiList(resolveApiList(gwClient.getApiList(), outcome));
|
||||||
|
|
||||||
|
credentialService.save(credential);
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyGwFields(ClientEntity gwClient, Credential credential) {
|
||||||
|
credential.setClientname(gwClient.getClientname());
|
||||||
|
credential.setClientsecret(gwClient.getClientsecret());
|
||||||
|
credential.setScope(gwClient.getScope());
|
||||||
|
credential.setGranttypes(gwClient.getGranttypes());
|
||||||
|
credential.setAccesstokenvalidityseconds(gwClient.getAccesstokenvalidityseconds());
|
||||||
|
credential.setRefreshtokenvalidityseconds(gwClient.getRefreshtokenvalidityseconds());
|
||||||
|
credential.setAllowedips(gwClient.getAllowedips());
|
||||||
|
credential.setAuthorities(gwClient.getAuthorities());
|
||||||
|
credential.setRedirecturi(gwClient.getRedirecturi());
|
||||||
|
credential.setSecuritykey(gwClient.getSecuritykey());
|
||||||
|
credential.setAutoapprove(gwClient.getAutoapprove());
|
||||||
|
credential.setResourceids(gwClient.getResourceids());
|
||||||
|
credential.setOrgid(gwClient.getOrgid());
|
||||||
|
credential.setOrgname(gwClient.getOrgname());
|
||||||
|
credential.setDailytokenlimit(gwClient.getDailytokenlimit());
|
||||||
|
credential.setModifiedby(gwClient.getModifiedby());
|
||||||
|
credential.setModifiedon(gwClient.getModifiedon());
|
||||||
|
// appstatus 는 GW 값 그대로 복사(차단 상태도 포털에 반영). 공백/null 이면 정상(1)으로 간주.
|
||||||
|
credential.setAppstatus(StringUtils.isNotBlank(gwClient.getAppstatus()) ? gwClient.getAppstatus() : "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GW apiList 의 각 eaisvcname 을 포털 ApiSpecInfo(영속) 로 매핑, 미등록이면 자동생성 후 저장한다. */
|
||||||
|
private List<ApiSpecInfo> resolveApiList(List<EAIMessageEntity> gwApiList, Outcome outcome) {
|
||||||
|
List<ApiSpecInfo> result = new ArrayList<>();
|
||||||
|
if (gwApiList == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (EAIMessageEntity gwApi : gwApiList) {
|
||||||
|
String apiId = gwApi.getEaisvcname();
|
||||||
|
ApiSpecInfo spec = apiSpecInfoService.findById(apiId).orElse(null);
|
||||||
|
if (spec == null) {
|
||||||
|
spec = createApiSpec(apiId, gwApi, outcome);
|
||||||
|
}
|
||||||
|
result.add(spec);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiSpecInfo createApiSpec(String apiId, EAIMessageEntity gwApi, Outcome outcome) {
|
||||||
|
ApiSpecInfoUI ui;
|
||||||
|
try {
|
||||||
|
ui = autoGenService.generateApiSpecInfo(apiId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("API Spec 자동생성 실패, 최소 정보로 대체 생성 - apiId: {}", apiId, e);
|
||||||
|
ui = new ApiSpecInfoUI();
|
||||||
|
ui.setApiId(apiId);
|
||||||
|
ui.setApiName(StringUtils.isNotBlank(gwApi.getEaisvcdesc()) ? gwApi.getEaisvcdesc() : apiId);
|
||||||
|
ui.setApiSimpleDescription(gwApi.getEaisvcdesc());
|
||||||
|
outcome.specGenFailed.add(apiId);
|
||||||
|
}
|
||||||
|
ui.setDisplayYn("N"); // 비공개로 자동 생성 - 관리자가 API Spec 관리에서 확인 후 공개
|
||||||
|
apiSpecManService.save(ui);
|
||||||
|
outcome.createdApiSpecs.add(apiId);
|
||||||
|
|
||||||
|
return apiSpecInfoService.findById(apiId)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("API Spec 저장 직후 조회 실패: " + apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Outcome {
|
||||||
|
private final boolean inserted;
|
||||||
|
private final List<String> createdApiSpecs = new ArrayList<>();
|
||||||
|
private final List<String> specGenFailed = new ArrayList<>();
|
||||||
|
|
||||||
|
Outcome(boolean inserted) {
|
||||||
|
this.inserted = inserted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -72,6 +72,7 @@ public class MessageRequestManService extends BaseService {
|
|||||||
|
|
||||||
ui.setUmsUid(e.getUmsUid());
|
ui.setUmsUid(e.getUmsUid());
|
||||||
ui.setRequestStatus(e.getRequestStatus());
|
ui.setRequestStatus(e.getRequestStatus());
|
||||||
|
ui.setResponseData(e.getResponseData());
|
||||||
ui.setEaiInterfaceId(e.getEaiInterfaceId());
|
ui.setEaiInterfaceId(e.getEaiInterfaceId());
|
||||||
ui.setServiceId(e.getServiceId());
|
ui.setServiceId(e.getServiceId());
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ public class MessageRequestUI {
|
|||||||
/* 본문 — 패턴 마스킹 + span 색상강조된 안전 HTML */
|
/* 본문 — 패턴 마스킹 + span 색상강조된 안전 HTML */
|
||||||
private String messageHtml;
|
private String messageHtml;
|
||||||
|
|
||||||
|
/* UMS 응답 내용 (RESPONSE_DATA) — 원문 그대로, 화면에서는 text 로만 출력 */
|
||||||
|
private String responseData;
|
||||||
|
|
||||||
/* 수신자 (마스킹) */
|
/* 수신자 (마스킹) */
|
||||||
private String username;
|
private String username;
|
||||||
private String email;
|
private String email;
|
||||||
|
|||||||
@@ -147,8 +147,8 @@ public class UmsDispatchService {
|
|||||||
try {
|
try {
|
||||||
transactionTemplate.execute(status -> {
|
transactionTemplate.execute(status -> {
|
||||||
try {
|
try {
|
||||||
|
// 상태(SENT/FAILED) 및 RESPONSE_DATA 저장은 umsService.send() 내부에서 처리한다.
|
||||||
umsService.send(message);
|
umsService.send(message);
|
||||||
updateMessageStatus(message, "SENT");
|
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// checked exception을 RuntimeException으로 감싸 전파 → TransactionTemplate이 rollback 후 re-throw
|
// checked exception을 RuntimeException으로 감싸 전파 → TransactionTemplate이 rollback 후 re-throw
|
||||||
@@ -159,7 +159,8 @@ public class UmsDispatchService {
|
|||||||
log.error("Failed to process message: {} - Error: {}", message.getId(), e.getMessage(), e);
|
log.error("Failed to process message: {} - Error: {}", message.getId(), e.getMessage(), e);
|
||||||
try {
|
try {
|
||||||
transactionTemplate.execute(s -> {
|
transactionTemplate.execute(s -> {
|
||||||
updateMessageStatus(message, "FAILED");
|
// 응답 자체를 받지 못한 경우(네트워크/타임아웃 등) 예외 메시지를 RESPONSE_DATA 에 남긴다.
|
||||||
|
updateMessageStatus(message, "FAILED", toResponseData(e));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -171,10 +172,19 @@ public class UmsDispatchService {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void updateMessageStatus(MessageRequest message, String status) {
|
public void updateMessageStatus(MessageRequest message, String status, String responseData) {
|
||||||
message.setRequestStatus(status);
|
message.setRequestStatus(status);
|
||||||
message.setSentDate(LocalDateTime.now());
|
message.setSentDate(LocalDateTime.now());
|
||||||
|
if (responseData != null) {
|
||||||
|
message.setResponseData(responseData);
|
||||||
|
}
|
||||||
entityManager.merge(message);
|
entityManager.merge(message);
|
||||||
entityManager.flush();
|
entityManager.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 예외를 RESPONSE_DATA 컬럼에 저장할 문자열로 변환한다. (RuntimeException 래퍼는 원인 예외로 풀어서 사용) */
|
||||||
|
private static String toResponseData(Throwable t) {
|
||||||
|
Throwable root = (t instanceof RuntimeException && t.getCause() != null) ? t.getCause() : t;
|
||||||
|
return String.valueOf(root);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +162,11 @@ public class ClientController extends OnlBaseAnnotationController {
|
|||||||
response.put("createdApiSpecCount", result.getCreatedApiSpecCount());
|
response.put("createdApiSpecCount", result.getCreatedApiSpecCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spec 자동생성 실패(레이아웃/어댑터 미설정 등)로 최소 정보만 생성된 API 목록
|
||||||
|
if (result.getSpecGenFailed() != null && !result.getSpecGenFailed().isEmpty()) {
|
||||||
|
response.put("specGenFailed", result.getSpecGenFailed());
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 에러 처리
|
// 에러 처리
|
||||||
|
|||||||
+14
-135
@@ -1,21 +1,12 @@
|
|||||||
package com.eactive.eai.rms.onl.manage.authserver.client;
|
package com.eactive.eai.rms.onl.manage.authserver.client;
|
||||||
|
|
||||||
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
|
|
||||||
import com.eactive.apim.portal.apispec.service.ApiSpecInfoService;
|
|
||||||
import com.eactive.apim.portal.app.entity.Credential;
|
|
||||||
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
|
||||||
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
|
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
|
||||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
|
||||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
|
||||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
|
||||||
import com.eactive.eai.rms.data.entity.onl.apim.portalorg.PortalOrgService;
|
import com.eactive.eai.rms.data.entity.onl.apim.portalorg.PortalOrgService;
|
||||||
import com.eactive.eai.rms.data.entity.onl.eaimsg.EAIMessageService;
|
import com.eactive.eai.rms.data.entity.onl.eaimsg.EAIMessageService;
|
||||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialManService;
|
import com.eactive.eai.rms.onl.apim.approval.credential.PortalCredentialSyncService;
|
||||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialService;
|
|
||||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
|
|
||||||
import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgUI;
|
import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgUI;
|
||||||
import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgUIMapper;
|
import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgUIMapper;
|
||||||
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -28,7 +19,6 @@ import com.eactive.eai.rms.common.base.OnlBaseService;
|
|||||||
import com.eactive.eai.rms.data.entity.onl.authserver.ClientEntityService;
|
import com.eactive.eai.rms.data.entity.onl.authserver.ClientEntityService;
|
||||||
import com.eactive.eai.rms.data.entity.onl.authserver.ClientSearch;
|
import com.eactive.eai.rms.data.entity.onl.authserver.ClientSearch;
|
||||||
|
|
||||||
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;
|
||||||
@@ -42,28 +32,22 @@ public class ClientManService extends OnlBaseService {
|
|||||||
private ClientUIMapper clientUIMapper;
|
private ClientUIMapper clientUIMapper;
|
||||||
private PortalOrgService portalOrgService;
|
private PortalOrgService portalOrgService;
|
||||||
private PortalOrgUIMapper portalOrgUIMapper;
|
private PortalOrgUIMapper portalOrgUIMapper;
|
||||||
private CredentialManService credentialManService;
|
|
||||||
private CredentialService credentialService;
|
|
||||||
private EAIMessageService eaiMessageService;
|
private EAIMessageService eaiMessageService;
|
||||||
private ApiSpecInfoService apiSpecInfoService;
|
private PortalCredentialSyncService portalCredentialSyncService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ClientManService(ClientEntityService clientEntityService,
|
public ClientManService(ClientEntityService clientEntityService,
|
||||||
ClientUIMapper clientUIMapper,
|
ClientUIMapper clientUIMapper,
|
||||||
PortalOrgService portalOrgService,
|
PortalOrgService portalOrgService,
|
||||||
PortalOrgUIMapper portalOrgUIMapper,
|
PortalOrgUIMapper portalOrgUIMapper,
|
||||||
CredentialManService credentialManService,
|
|
||||||
CredentialService credentialService,
|
|
||||||
EAIMessageService eaiMessageService,
|
EAIMessageService eaiMessageService,
|
||||||
ApiSpecInfoService apiSpecInfoService) {
|
PortalCredentialSyncService portalCredentialSyncService) {
|
||||||
this.clientEntityService = clientEntityService;
|
this.clientEntityService = clientEntityService;
|
||||||
this.clientUIMapper = clientUIMapper;
|
this.clientUIMapper = clientUIMapper;
|
||||||
this.portalOrgService = portalOrgService;
|
this.portalOrgService = portalOrgService;
|
||||||
this.portalOrgUIMapper = portalOrgUIMapper;
|
this.portalOrgUIMapper = portalOrgUIMapper;
|
||||||
this.credentialManService = credentialManService;
|
|
||||||
this.credentialService = credentialService;
|
|
||||||
this.eaiMessageService = eaiMessageService;
|
this.eaiMessageService = eaiMessageService;
|
||||||
this.apiSpecInfoService = apiSpecInfoService;
|
this.portalCredentialSyncService = portalCredentialSyncService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -127,6 +111,10 @@ public class ClientManService extends OnlBaseService {
|
|||||||
* AGW 인증관리(ClientEntity)의 정보를 개발자포탈(Credential)에 반영합니다.
|
* AGW 인증관리(ClientEntity)의 정보를 개발자포탈(Credential)에 반영합니다.
|
||||||
* 포탈에 없으면 신규 생성(INSERT), 있으면 업데이트(UPDATE)합니다.
|
* 포탈에 없으면 신규 생성(INSERT), 있으면 업데이트(UPDATE)합니다.
|
||||||
*
|
*
|
||||||
|
* 검증(2,3단계)은 APIGW 스키마에서 이 메서드의 트랜잭션 안에서 수행하고, 실패 시 포탈은 전혀 건드리지
|
||||||
|
* 않는다. 실제 포탈 쓰기(Credential/ApiSpecInfo)는 {@link PortalCredentialSyncService}
|
||||||
|
* (별도 EMS 트랜잭션)에 위임한다.
|
||||||
|
*
|
||||||
* @param clientId 클라이언트 ID
|
* @param clientId 클라이언트 ID
|
||||||
* @return 반영 결과 (INSERTED, UPDATED, SKIPPED_NO_ORG, SKIPPED_INVALID_API, ERROR)
|
* @return 반영 결과 (INSERTED, UPDATED, SKIPPED_NO_ORG, SKIPPED_INVALID_API, ERROR)
|
||||||
*/
|
*/
|
||||||
@@ -181,73 +169,20 @@ public class ClientManService extends OnlBaseService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. MONITORING 스키마로 전환
|
// 4. 포탈 스키마에 반영 (별도 EMS 트랜잭션)
|
||||||
DataSourceType monitoringType = DataSourceTypeManager.getDataSourceType("MONITORING");
|
PortalCredentialSyncService.Outcome outcome = portalCredentialSyncService.apply(clientEntity);
|
||||||
DataSourceContextHolder.setDataSourceType(monitoringType);
|
|
||||||
|
|
||||||
// 5. PTL_API_SPEC_INFO에 없는 API 자동 생성
|
|
||||||
List<String> createdApiSpecs = new ArrayList<>();
|
|
||||||
if (clientEntity.getApiList() != null && !clientEntity.getApiList().isEmpty()) {
|
|
||||||
for (EAIMessageEntity apiEntity : clientEntity.getApiList()) {
|
|
||||||
String apiId = apiEntity.getEaisvcname();
|
|
||||||
if (!apiSpecInfoService.findById(apiId).isPresent()) {
|
|
||||||
// PTL_API_SPEC_INFO에 없으면 자동 생성
|
|
||||||
ApiSpecInfo newApiSpec = new ApiSpecInfo();
|
|
||||||
newApiSpec.setApiId(apiId);
|
|
||||||
newApiSpec.setApiName(StringUtils.isNotBlank(apiEntity.getEaisvcdesc())
|
|
||||||
? apiEntity.getEaisvcdesc() : apiId);
|
|
||||||
newApiSpec.setApiSimpleDescription(apiEntity.getEaisvcdesc());
|
|
||||||
newApiSpec.setDisplayYn("Y"); // 기본값: 공개
|
|
||||||
apiSpecInfoService.create(newApiSpec);
|
|
||||||
createdApiSpecs.add(apiId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 기존 Credential 확인
|
|
||||||
Credential existingCredential = credentialService.findByClientidAndOrgid(clientId, orgId);
|
|
||||||
|
|
||||||
// 7. 데이터 변환 (ClientEntity → CredentialUI)
|
|
||||||
CredentialUI credentialUI = convertToCredentialUI(clientEntity);
|
|
||||||
|
|
||||||
SyncResult.SyncAction action;
|
|
||||||
if (existingCredential != null) {
|
|
||||||
// 업데이트: 기존 값 유지해야 할 필드 처리
|
|
||||||
Credential existing = existingCredential;
|
|
||||||
if (StringUtils.isNotBlank(existing.getAppDescription())) {
|
|
||||||
credentialUI.setAppDescription(existing.getAppDescription());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(existing.getAppIconFileId())) {
|
|
||||||
credentialUI.setAppIconFileId(existing.getAppIconFileId());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(existing.getServer())) {
|
|
||||||
credentialUI.setServer(existing.getServer());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. 포탈 스키마에 업데이트
|
|
||||||
credentialManService.update(credentialUI);
|
|
||||||
action = SyncResult.SyncAction.UPDATED;
|
|
||||||
} else {
|
|
||||||
// 신규 생성: 기본값 설정
|
|
||||||
if (StringUtils.isBlank(credentialUI.getAppDescription())) {
|
|
||||||
credentialUI.setAppDescription("APIGW에서 반영된 인증정보");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. 포탈 스키마에 신규 생성
|
|
||||||
credentialManService.insert(credentialUI);
|
|
||||||
action = SyncResult.SyncAction.INSERTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SyncResult.builder()
|
return SyncResult.builder()
|
||||||
.success(true)
|
.success(true)
|
||||||
.action(action)
|
.action(outcome.isInserted() ? SyncResult.SyncAction.INSERTED : SyncResult.SyncAction.UPDATED)
|
||||||
.message("포탈 정보 반영 완료")
|
.message("포탈 정보 반영 완료")
|
||||||
.targetApis(requestedApiIds)
|
.targetApis(requestedApiIds)
|
||||||
.targetApiCount(requestedApiIds.size())
|
.targetApiCount(requestedApiIds.size())
|
||||||
.syncedApis(requestedApiIds)
|
.syncedApis(requestedApiIds)
|
||||||
.syncedApiCount(requestedApiIds.size())
|
.syncedApiCount(requestedApiIds.size())
|
||||||
.createdApiSpecs(createdApiSpecs)
|
.createdApiSpecs(outcome.getCreatedApiSpecs())
|
||||||
.createdApiSpecCount(createdApiSpecs.size())
|
.createdApiSpecCount(outcome.getCreatedApiSpecs().size())
|
||||||
|
.specGenFailed(outcome.getSpecGenFailed())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -259,60 +194,4 @@ public class ClientManService extends OnlBaseService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* ClientEntity를 CredentialUI로 변환합니다.
|
|
||||||
*
|
|
||||||
* @param clientEntity APIGW 클라이언트 엔티티
|
|
||||||
* @return CredentialUI 포탈 인증정보 UI 객체
|
|
||||||
*/
|
|
||||||
private CredentialUI convertToCredentialUI(ClientEntity clientEntity) {
|
|
||||||
CredentialUI credentialUI = new CredentialUI();
|
|
||||||
|
|
||||||
// 기본 인증정보
|
|
||||||
credentialUI.setClientid(clientEntity.getClientid());
|
|
||||||
credentialUI.setClientname(clientEntity.getClientname());
|
|
||||||
credentialUI.setClientsecret(clientEntity.getClientsecret());
|
|
||||||
|
|
||||||
// OAuth 설정
|
|
||||||
credentialUI.setScope(clientEntity.getScope());
|
|
||||||
credentialUI.setGranttypes(clientEntity.getGranttypes());
|
|
||||||
credentialUI.setAccesstokenvalidityseconds(clientEntity.getAccesstokenvalidityseconds());
|
|
||||||
credentialUI.setRefreshtokenvalidityseconds(clientEntity.getRefreshtokenvalidityseconds());
|
|
||||||
|
|
||||||
// 보안 설정
|
|
||||||
credentialUI.setAllowedips(clientEntity.getAllowedips());
|
|
||||||
credentialUI.setAuthorities(clientEntity.getAuthorities());
|
|
||||||
credentialUI.setRedirecturi(clientEntity.getRedirecturi());
|
|
||||||
credentialUI.setSecuritykey(clientEntity.getSecuritykey());
|
|
||||||
credentialUI.setAutoapprove(clientEntity.getAutoapprove());
|
|
||||||
credentialUI.setResourceids(clientEntity.getResourceids());
|
|
||||||
|
|
||||||
// 조직 정보
|
|
||||||
credentialUI.setOrgid(clientEntity.getOrgid());
|
|
||||||
credentialUI.setOrgname(clientEntity.getOrgname());
|
|
||||||
|
|
||||||
// 앱 상태
|
|
||||||
credentialUI.setAppstatus(clientEntity.getAppstatus());
|
|
||||||
credentialUI.setDailytokenlimit(clientEntity.getDailytokenlimit());
|
|
||||||
|
|
||||||
// 수정 정보
|
|
||||||
credentialUI.setModifiedby(clientEntity.getModifiedby());
|
|
||||||
if (clientEntity.getModifiedon() != null) {
|
|
||||||
credentialUI.setModifiedon(clientEntity.getModifiedon().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
|
||||||
}
|
|
||||||
|
|
||||||
// API 목록 변환
|
|
||||||
List<ApiSpecInfoUI> apiList = new ArrayList<>();
|
|
||||||
if (clientEntity.getApiList() != null) {
|
|
||||||
for (EAIMessageEntity apiEntity : clientEntity.getApiList()) {
|
|
||||||
ApiSpecInfoUI apiUI = new ApiSpecInfoUI();
|
|
||||||
apiUI.setApiId(apiEntity.getEaisvcname());
|
|
||||||
apiList.add(apiUI);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
credentialUI.setApiList(apiList);
|
|
||||||
|
|
||||||
return credentialUI;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ public class SyncResult {
|
|||||||
*/
|
*/
|
||||||
private int createdApiSpecCount;
|
private int createdApiSpecCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spec 자동생성에 실패해 최소 정보(apiId만)로 대체 생성된 API 목록
|
||||||
|
* (레이아웃/어댑터 정보 부재 등. PTL_API_SPEC_INFO 행 자체는 생성됨 - displayYn='N')
|
||||||
|
*/
|
||||||
|
private List<String> specGenFailed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 반영 액션 타입
|
* 반영 액션 타입
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -135,6 +135,15 @@ public class ApiInterfaceService extends OnlBaseService {
|
|||||||
public Page<ApiInterfaceUI> selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable,String sortname, String sortorder) {
|
public Page<ApiInterfaceUI> selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable,String sortname, String sortorder) {
|
||||||
|
|
||||||
Page<Tuple> tuples = eaiMessageQueryService.selectList(eaiMessageUISearch, pageable, sortname, sortorder );
|
Page<Tuple> tuples = eaiMessageQueryService.selectList(eaiMessageUISearch, pageable, sortname, sortorder );
|
||||||
|
List<String> apiIds = tuples.getContent().stream()
|
||||||
|
.map(tuple -> tuple.get(QEAIMessageEntity.eAIMessageEntity).getEaisvcname())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<String, String> specStatuses = eaiMessageQueryService.findApiSpecStatuses(apiIds);
|
||||||
|
List<String> publishedApiIds = apiIds.stream()
|
||||||
|
.filter(apiId -> "PUBLISHED".equals(specStatuses.get(apiId)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<String, List<String>> publishedGroupNames = apiGroupService.findPublishedGroupNamesByApiIds(publishedApiIds);
|
||||||
|
|
||||||
return tuples.map(tuple -> {
|
return tuples.map(tuple -> {
|
||||||
EAIMessageEntity eaiMessageEntity = tuple.get(QEAIMessageEntity.eAIMessageEntity);
|
EAIMessageEntity eaiMessageEntity = tuple.get(QEAIMessageEntity.eAIMessageEntity);
|
||||||
@@ -153,6 +162,8 @@ public class ApiInterfaceService extends OnlBaseService {
|
|||||||
apiInterfaceUI.setApiFullPath(apiFullPath);
|
apiInterfaceUI.setApiFullPath(apiFullPath);
|
||||||
apiInterfaceUI.setBzwksvckeyname(bzwksvckeyname);
|
apiInterfaceUI.setBzwksvckeyname(bzwksvckeyname);
|
||||||
apiInterfaceUI.setStatusCode(statusCode);
|
apiInterfaceUI.setStatusCode(statusCode);
|
||||||
|
apiInterfaceUI.setSpecStatus(specStatuses.getOrDefault(eaiMessageEntity.getEaisvcname(), "UNREGISTERED"));
|
||||||
|
apiInterfaceUI.setPublishedGroupNames(publishedGroupNames.getOrDefault(eaiMessageEntity.getEaisvcname(), Collections.emptyList()));
|
||||||
|
|
||||||
return apiInterfaceUI;
|
return apiInterfaceUI;
|
||||||
});
|
});
|
||||||
@@ -164,6 +175,7 @@ public class ApiInterfaceService extends OnlBaseService {
|
|||||||
// 전체 목록수 얻기
|
// 전체 목록수 얻기
|
||||||
HashMap map = new HashMap();
|
HashMap map = new HashMap();
|
||||||
map.put("bizList", voList);
|
map.put("bizList", voList);
|
||||||
|
map.put("apiGroupList", apiGroupService.findGroupOptions());
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package com.eactive.eai.rms.onl.transaction.apim;
|
||||||
|
|
||||||
|
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutUI;
|
||||||
|
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiInterfaceUI;
|
||||||
|
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 레이아웃+어댑터 기반 API Spec(OpenAPI 3.1) 자동생성 규칙(백엔드 단일 소스).
|
||||||
|
*
|
||||||
|
* {@link DjbApiSpecController}(6단계 편집 마법사)와 인증관리 "개발자포탈 정보 반영"
|
||||||
|
* (ClientManService.syncPortalData → PortalCredentialSyncService) 양쪽이 이 서비스를 공유한다.
|
||||||
|
* 규칙 자체는 원래 DjbApiSpecController 의 private 메서드였던 것을 그대로 옮긴 것으로, 동작 변경은 없다.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class DjbApiSpecAutoGenService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DjbApiSpecAutoGenService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApiInterfaceService apiInterfaceService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApiSpecManService apiSpecManService;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 레이아웃/어댑터 정보를 조합해 OpenAPI 3.1 스펙 JSON 을 생성한다.
|
||||||
|
* 규칙: title/tag/summary = API명(eaiSvcDesc), operationId = 인터페이스ID,
|
||||||
|
* servers[0].url 을 path 앞에 붙이고 servers 제거(서버 선택 제거),
|
||||||
|
* 요청/200 응답 예제를 레이아웃 샘플로 임베드.
|
||||||
|
*/
|
||||||
|
public String generateSpecJson(String eaiSvcName) throws Exception {
|
||||||
|
ApiInterfaceUI apiInterfaceUI = apiInterfaceService.selectDetail(eaiSvcName);
|
||||||
|
if (apiInterfaceUI == null) {
|
||||||
|
throw new IllegalStateException("API 인터페이스 정보를 찾을 수 없습니다: " + eaiSvcName);
|
||||||
|
}
|
||||||
|
LayoutUI requestLayoutUI = resolveLayout(apiInterfaceUI, true);
|
||||||
|
LayoutUI responseLayoutUI = resolveLayout(apiInterfaceUI, false);
|
||||||
|
return generateSpecJson(eaiSvcName, apiInterfaceUI, requestLayoutUI, responseLayoutUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateSpecJson(String eaiSvcName, ApiInterfaceUI apiInterfaceUI,
|
||||||
|
LayoutUI requestLayoutUI, LayoutUI responseLayoutUI) throws Exception {
|
||||||
|
Map<String, String> inboundAdapterSpec = apiInterfaceService.getHttpAdapterInfo(apiInterfaceUI.getFromAdapter());
|
||||||
|
if (inboundAdapterSpec == null) {
|
||||||
|
inboundAdapterSpec = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
String baseSpec = apiSpecManService.generateSwaggerSpec(requestLayoutUI, responseLayoutUI, inboundAdapterSpec, apiInterfaceUI);
|
||||||
|
|
||||||
|
String apiName = StringUtils.isNotBlank(apiInterfaceUI.getEaiSvcDesc()) ? apiInterfaceUI.getEaiSvcDesc() : eaiSvcName;
|
||||||
|
String contentType = StringUtils.isNotBlank(apiInterfaceUI.getRestContentType()) ? apiInterfaceUI.getRestContentType() : "application/json";
|
||||||
|
return applyAutoRules(baseSpec, apiName, eaiSvcName, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 자동생성 결과를 저장 가능한 {@link ApiSpecInfoUI} 로 조립한다.
|
||||||
|
* displayYn 은 호출자가 정한다(비공개로 자동 생성하려면 호출 후 "N" 을 세팅).
|
||||||
|
*/
|
||||||
|
public ApiSpecInfoUI generateApiSpecInfo(String eaiSvcName) throws Exception {
|
||||||
|
ApiInterfaceUI apiInterfaceUI = apiInterfaceService.selectDetail(eaiSvcName);
|
||||||
|
if (apiInterfaceUI == null) {
|
||||||
|
throw new IllegalStateException("API 인터페이스 정보를 찾을 수 없습니다: " + eaiSvcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String apiName = StringUtils.isNotBlank(apiInterfaceUI.getEaiSvcDesc()) ? apiInterfaceUI.getEaiSvcDesc() : eaiSvcName;
|
||||||
|
LayoutUI requestLayoutUI = resolveLayout(apiInterfaceUI, true);
|
||||||
|
LayoutUI responseLayoutUI = resolveLayout(apiInterfaceUI, false);
|
||||||
|
String specJson = generateSpecJson(eaiSvcName, apiInterfaceUI, requestLayoutUI, responseLayoutUI);
|
||||||
|
|
||||||
|
ApiSpecInfoUI ui = new ApiSpecInfoUI();
|
||||||
|
ui.setApiId(eaiSvcName);
|
||||||
|
ui.setApiName(apiName);
|
||||||
|
ui.setApiSimpleDescription(apiInterfaceUI.getEaiSvcDesc());
|
||||||
|
ui.setTestbedSpec(specJson);
|
||||||
|
ui.setApiRequestSpec(apiSpecManService.generateSpecTableFromLayout(requestLayoutUI));
|
||||||
|
ui.setApiResponseSpec(apiSpecManService.generateSpecTableFromLayout(responseLayoutUI));
|
||||||
|
ui.setSampleRequest(apiSpecManService.generateSampleDataFromLayout(requestLayoutUI));
|
||||||
|
ui.setSampleResponse(apiSpecManService.generateSampleDataFromLayout(responseLayoutUI));
|
||||||
|
|
||||||
|
extractFirstOperation(specJson, ui);
|
||||||
|
return ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 생성된 spec 의 첫 path/method/requestBody mediaType 을 추출한다(마법사 프론트와 동일 규칙, app.js 의 _defScalar 대응). */
|
||||||
|
private void extractFirstOperation(String specJson, ApiSpecInfoUI ui) {
|
||||||
|
try {
|
||||||
|
JsonNode root = objectMapper.readTree(specJson);
|
||||||
|
JsonNode pathsNode = root.get("paths");
|
||||||
|
if (pathsNode == null || !pathsNode.isObject() || pathsNode.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> pathIt = pathsNode.fields();
|
||||||
|
if (!pathIt.hasNext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map.Entry<String, JsonNode> pathEntry = pathIt.next();
|
||||||
|
ui.setApiUrl(pathEntry.getKey());
|
||||||
|
|
||||||
|
JsonNode pathItem = pathEntry.getValue();
|
||||||
|
if (pathItem != null && pathItem.isObject() && pathItem.fields().hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> methodEntry = pathItem.fields().next();
|
||||||
|
ui.setApiMethod(methodEntry.getKey().toUpperCase());
|
||||||
|
|
||||||
|
JsonNode op = methodEntry.getValue();
|
||||||
|
if (op != null && op.isObject()) {
|
||||||
|
JsonNode content = op.path("requestBody").path("content");
|
||||||
|
if (content.isObject() && content.fields().hasNext()) {
|
||||||
|
ui.setApiContentType(content.fields().next().getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("자동생성 spec 에서 apiUrl/apiMethod 추출 실패: " + ui.getApiId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LayoutUI resolveLayout(ApiInterfaceUI apiInterfaceUI, boolean request) throws Exception {
|
||||||
|
String layoutName = request ? apiInterfaceUI.getInboundRequestLayout() : apiInterfaceUI.getInboundResponseLayout();
|
||||||
|
return StringUtils.isNotEmpty(layoutName) ? apiSpecManService.selectLayoutUI(layoutName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 자동생성 규칙 후처리(Jackson 트리 조작). 실패 시 원본 유지. */
|
||||||
|
private String applyAutoRules(String specJson, String apiName, String eaiSvcName, String contentType) {
|
||||||
|
try {
|
||||||
|
JsonNode parsed = objectMapper.readTree(specJson);
|
||||||
|
if (!parsed.isObject()) {
|
||||||
|
return specJson;
|
||||||
|
}
|
||||||
|
ObjectNode root = (ObjectNode) parsed;
|
||||||
|
|
||||||
|
root.put("openapi", "3.1.0"); // OpenAPI 3.1 방출(Swagger UI 5.x oas31 렌더 지원)
|
||||||
|
getOrCreateObject(root, "info").put("title", apiName);
|
||||||
|
|
||||||
|
String tagName = "DJBank"; // 자동생성 태그명 고정
|
||||||
|
ArrayNode tags = objectMapper.createArrayNode();
|
||||||
|
ObjectNode tag = objectMapper.createObjectNode();
|
||||||
|
tag.put("name", tagName);
|
||||||
|
tag.put("description", apiName); // 태그 설명 = 간단설명(API명)
|
||||||
|
tags.add(tag);
|
||||||
|
root.set("tags", tags);
|
||||||
|
|
||||||
|
// 어댑터경로(servers[0].url = getHttpAdapterInfo urlPath)를 path 앞에 baking 하고 servers 제거.
|
||||||
|
// 호스트(gw=djb.gateway.base-url / mock=Mock URL / sample=없음)는 프론트가 응답유형에 따라 서버로 붙인다.
|
||||||
|
String adapterPath = "";
|
||||||
|
JsonNode serversNode = root.get("servers");
|
||||||
|
if (serversNode != null && serversNode.isArray() && serversNode.size() > 0 && serversNode.get(0).get("url") != null) {
|
||||||
|
adapterPath = serversNode.get(0).get("url").asText("");
|
||||||
|
}
|
||||||
|
root.remove("servers");
|
||||||
|
|
||||||
|
JsonNode pathsNode = root.get("paths");
|
||||||
|
if (pathsNode != null && pathsNode.isObject()) {
|
||||||
|
ObjectNode paths = (ObjectNode) pathsNode;
|
||||||
|
List<String> pathKeys = new ArrayList<>();
|
||||||
|
Iterator<String> pit = paths.fieldNames();
|
||||||
|
while (pit.hasNext()) {
|
||||||
|
pathKeys.add(pit.next());
|
||||||
|
}
|
||||||
|
for (String pk : pathKeys) {
|
||||||
|
JsonNode pathItem = paths.get(pk);
|
||||||
|
if (pathItem != null && pathItem.isObject()) {
|
||||||
|
List<String> methods = new ArrayList<>();
|
||||||
|
Iterator<String> mit = pathItem.fieldNames();
|
||||||
|
while (mit.hasNext()) {
|
||||||
|
methods.add(mit.next());
|
||||||
|
}
|
||||||
|
for (String mk : methods) {
|
||||||
|
JsonNode opNode = pathItem.get(mk);
|
||||||
|
if (opNode != null && opNode.isObject()) {
|
||||||
|
ObjectNode op = (ObjectNode) opNode;
|
||||||
|
op.put("operationId", eaiSvcName);
|
||||||
|
op.put("summary", apiName);
|
||||||
|
ArrayNode opTags = objectMapper.createArrayNode();
|
||||||
|
opTags.add(tagName);
|
||||||
|
op.set("tags", opTags);
|
||||||
|
// 예제(요청/응답 본문)는 프론트에서 스키마 기준으로 생성 → 자동생성/재생성 결과 일치. 여기선 임베드하지 않음.
|
||||||
|
markGwOnOperation(op);
|
||||||
|
addResponseHeader(op, "200", "Content-Type", contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String newKey = adapterPath.isEmpty() ? pk
|
||||||
|
: (adapterPath.replaceAll("/+$", "") + (pk.startsWith("/") ? "" : "/") + pk);
|
||||||
|
if (!newKey.equals(pk)) {
|
||||||
|
paths.set(newKey, pathItem);
|
||||||
|
paths.remove(pk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objectMapper.writeValueAsString(root);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("applyAutoRules 실패, 원본 spec 유지", e);
|
||||||
|
return specJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GW 유래 스키마 필드에 x-djb-gw 마커 부여(프론트에서 잠금 표시). */
|
||||||
|
private void markGwOnOperation(ObjectNode op) {
|
||||||
|
JsonNode body = op.get("requestBody");
|
||||||
|
if (body != null && body.isObject()) {
|
||||||
|
markGwInContent(body.get("content"));
|
||||||
|
}
|
||||||
|
JsonNode resps = op.get("responses");
|
||||||
|
if (resps != null && resps.isObject()) {
|
||||||
|
List<String> codes = new ArrayList<>();
|
||||||
|
Iterator<String> it = resps.fieldNames();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
codes.add(it.next());
|
||||||
|
}
|
||||||
|
for (String c : codes) {
|
||||||
|
JsonNode r = resps.get(c);
|
||||||
|
if (r != null && r.isObject()) {
|
||||||
|
markGwInContent(r.get("content"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markGwInContent(JsonNode content) {
|
||||||
|
if (content == null || !content.isObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> mts = new ArrayList<>();
|
||||||
|
Iterator<String> it = content.fieldNames();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
mts.add(it.next());
|
||||||
|
}
|
||||||
|
for (String mt : mts) {
|
||||||
|
JsonNode m = content.get(mt);
|
||||||
|
if (m != null && m.isObject()) {
|
||||||
|
markGwSchema(m.get("schema"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markGwSchema(JsonNode schema) {
|
||||||
|
if (schema == null || !schema.isObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ObjectNode s = (ObjectNode) schema;
|
||||||
|
JsonNode props = s.get("properties");
|
||||||
|
if (props != null && props.isObject()) {
|
||||||
|
ArrayNode required = (s.get("required") != null && s.get("required").isArray())
|
||||||
|
? (ArrayNode) s.get("required") : objectMapper.createArrayNode();
|
||||||
|
java.util.Set<String> existing = new java.util.HashSet<>();
|
||||||
|
required.forEach(n -> existing.add(n.asText()));
|
||||||
|
List<String> keys = new ArrayList<>();
|
||||||
|
Iterator<String> it = props.fieldNames();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
keys.add(it.next());
|
||||||
|
}
|
||||||
|
for (String k : keys) {
|
||||||
|
JsonNode p = props.get(k);
|
||||||
|
if (p != null && p.isObject()) {
|
||||||
|
((ObjectNode) p).put("x-djb-gw", true);
|
||||||
|
if (!existing.contains(k)) { // GW 필드는 기본 '필수'
|
||||||
|
required.add(k);
|
||||||
|
existing.add(k);
|
||||||
|
}
|
||||||
|
markGwSchema(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (required.size() > 0) {
|
||||||
|
s.set("required", required);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonNode items = s.get("items");
|
||||||
|
if (items != null && items.isObject()) {
|
||||||
|
markGwSchema(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 응답 헤더 미리 등록(중복 시 스킵). */
|
||||||
|
private void addResponseHeader(ObjectNode op, String code, String headerName, String example) {
|
||||||
|
JsonNode resps = op.get("responses");
|
||||||
|
if (resps == null || !resps.isObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonNode r = resps.get(code);
|
||||||
|
if (r == null || !r.isObject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ObjectNode headers = getOrCreateObject((ObjectNode) r, "headers");
|
||||||
|
if (headers.has(headerName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ObjectNode h = objectMapper.createObjectNode();
|
||||||
|
h.put("description", headerName);
|
||||||
|
ObjectNode sc = objectMapper.createObjectNode();
|
||||||
|
sc.put("type", "string");
|
||||||
|
h.set("schema", sc);
|
||||||
|
h.put("example", example);
|
||||||
|
headers.set(headerName, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectNode getOrCreateObject(ObjectNode parent, String field) {
|
||||||
|
JsonNode node = parent.get(field);
|
||||||
|
if (node != null && node.isObject()) {
|
||||||
|
return (ObjectNode) node;
|
||||||
|
}
|
||||||
|
ObjectNode created = objectMapper.createObjectNode();
|
||||||
|
parent.set(field, created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,16 +2,13 @@ package com.eactive.eai.rms.onl.transaction.apim;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.StringEscapeUtils;
|
import org.apache.commons.lang.StringEscapeUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -60,6 +57,9 @@ public class DjbApiSpecController extends OnlBaseAnnotationController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private DjbApiSpecLayoutMergeService layoutMergeService;
|
private DjbApiSpecLayoutMergeService layoutMergeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DjbApiSpecAutoGenService autoGenService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PortalPropertyService portalPropertyService;
|
private PortalPropertyService portalPropertyService;
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ public class DjbApiSpecController extends OnlBaseAnnotationController {
|
|||||||
spec = saved.getTestbedSpec();
|
spec = saved.getTestbedSpec();
|
||||||
source = "saved";
|
source = "saved";
|
||||||
} else {
|
} else {
|
||||||
spec = generateSpec(eaiSvcName);
|
spec = autoGenService.generateSpecJson(eaiSvcName);
|
||||||
source = force ? "regenerated" : "generated";
|
source = force ? "regenerated" : "generated";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,268 +324,6 @@ public class DjbApiSpecController extends OnlBaseAnnotationController {
|
|||||||
return StringUtils.isNotEmpty(layoutName) ? apiSpecManService.selectLayoutUI(layoutName) : null;
|
return StringUtils.isNotEmpty(layoutName) ? apiSpecManService.selectLayoutUI(layoutName) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 레이아웃 기반 자동 생성 + 자동생성 규칙(백엔드 단일 소스).
|
|
||||||
* 규칙: title/tag/summary = API명(eaiSvcDesc), operationId = 인터페이스ID,
|
|
||||||
* servers[0].url 을 path 앞에 붙이고 servers 제거(서버 선택 제거),
|
|
||||||
* 요청/200 응답 예제를 레이아웃 샘플로 임베드.
|
|
||||||
*/
|
|
||||||
private String generateSpec(String eaiSvcName) throws Exception {
|
|
||||||
ApiInterfaceUI apiInterfaceUI = apiInterfaceService.selectDetail(eaiSvcName);
|
|
||||||
if (apiInterfaceUI == null) {
|
|
||||||
throw new IllegalStateException("API 인터페이스 정보를 찾을 수 없습니다: " + eaiSvcName);
|
|
||||||
}
|
|
||||||
Map<String, String> inboundAdapterSpec = apiInterfaceService.getHttpAdapterInfo(apiInterfaceUI.getFromAdapter());
|
|
||||||
if (inboundAdapterSpec == null) {
|
|
||||||
inboundAdapterSpec = new HashMap<>();
|
|
||||||
}
|
|
||||||
String requestLayoutName = apiInterfaceUI.getInboundRequestLayout();
|
|
||||||
String responseLayoutName = apiInterfaceUI.getInboundResponseLayout();
|
|
||||||
LayoutUI requestLayoutUI = StringUtils.isNotEmpty(requestLayoutName)
|
|
||||||
? apiSpecManService.selectLayoutUI(requestLayoutName) : null;
|
|
||||||
LayoutUI responseLayoutUI = StringUtils.isNotEmpty(responseLayoutName)
|
|
||||||
? apiSpecManService.selectLayoutUI(responseLayoutName) : null;
|
|
||||||
|
|
||||||
String baseSpec = apiSpecManService.generateSwaggerSpec(requestLayoutUI, responseLayoutUI, inboundAdapterSpec, apiInterfaceUI);
|
|
||||||
|
|
||||||
String apiName = StringUtils.isNotBlank(apiInterfaceUI.getEaiSvcDesc()) ? apiInterfaceUI.getEaiSvcDesc() : eaiSvcName;
|
|
||||||
String reqSample = apiSpecManService.generateSampleDataFromLayout(requestLayoutUI);
|
|
||||||
String resSample = apiSpecManService.generateSampleDataFromLayout(responseLayoutUI);
|
|
||||||
String contentType = StringUtils.isNotBlank(apiInterfaceUI.getRestContentType()) ? apiInterfaceUI.getRestContentType() : "application/json";
|
|
||||||
return applyAutoRules(baseSpec, apiName, eaiSvcName, reqSample, resSample, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 자동생성 규칙 후처리(Jackson 트리 조작). 실패 시 원본 유지. */
|
|
||||||
private String applyAutoRules(String specJson, String apiName, String eaiSvcName, String reqSample, String resSample, String contentType) {
|
|
||||||
try {
|
|
||||||
JsonNode parsed = objectMapper.readTree(specJson);
|
|
||||||
if (!parsed.isObject()) {
|
|
||||||
return specJson;
|
|
||||||
}
|
|
||||||
ObjectNode root = (ObjectNode) parsed;
|
|
||||||
|
|
||||||
root.put("openapi", "3.1.0"); // OpenAPI 3.1 방출(Swagger UI 5.x oas31 렌더 지원)
|
|
||||||
getOrCreateObject(root, "info").put("title", apiName);
|
|
||||||
|
|
||||||
String tagName = "DJBank"; // 자동생성 태그명 고정
|
|
||||||
ArrayNode tags = objectMapper.createArrayNode();
|
|
||||||
ObjectNode tag = objectMapper.createObjectNode();
|
|
||||||
tag.put("name", tagName);
|
|
||||||
tag.put("description", apiName); // 태그 설명 = 간단설명(API명)
|
|
||||||
tags.add(tag);
|
|
||||||
root.set("tags", tags);
|
|
||||||
|
|
||||||
// 어댑터경로(servers[0].url = getHttpAdapterInfo urlPath)를 path 앞에 baking 하고 servers 제거.
|
|
||||||
// 호스트(gw=djb.gateway.base-url / mock=Mock URL / sample=없음)는 프론트가 응답유형에 따라 서버로 붙인다.
|
|
||||||
String adapterPath = "";
|
|
||||||
JsonNode serversNode = root.get("servers");
|
|
||||||
if (serversNode != null && serversNode.isArray() && serversNode.size() > 0 && serversNode.get(0).get("url") != null) {
|
|
||||||
adapterPath = serversNode.get(0).get("url").asText("");
|
|
||||||
}
|
|
||||||
root.remove("servers");
|
|
||||||
|
|
||||||
JsonNode pathsNode = root.get("paths");
|
|
||||||
if (pathsNode != null && pathsNode.isObject()) {
|
|
||||||
ObjectNode paths = (ObjectNode) pathsNode;
|
|
||||||
List<String> pathKeys = new ArrayList<>();
|
|
||||||
Iterator<String> pit = paths.fieldNames();
|
|
||||||
while (pit.hasNext()) {
|
|
||||||
pathKeys.add(pit.next());
|
|
||||||
}
|
|
||||||
for (String pk : pathKeys) {
|
|
||||||
JsonNode pathItem = paths.get(pk);
|
|
||||||
if (pathItem != null && pathItem.isObject()) {
|
|
||||||
List<String> methods = new ArrayList<>();
|
|
||||||
Iterator<String> mit = pathItem.fieldNames();
|
|
||||||
while (mit.hasNext()) {
|
|
||||||
methods.add(mit.next());
|
|
||||||
}
|
|
||||||
for (String mk : methods) {
|
|
||||||
JsonNode opNode = pathItem.get(mk);
|
|
||||||
if (opNode != null && opNode.isObject()) {
|
|
||||||
ObjectNode op = (ObjectNode) opNode;
|
|
||||||
op.put("operationId", eaiSvcName);
|
|
||||||
op.put("summary", apiName);
|
|
||||||
ArrayNode opTags = objectMapper.createArrayNode();
|
|
||||||
opTags.add(tagName);
|
|
||||||
op.set("tags", opTags);
|
|
||||||
// 예제(요청/응답 본문)는 프론트에서 스키마 기준으로 생성 → 자동생성/재생성 결과 일치. 여기선 임베드하지 않음.
|
|
||||||
markGwOnOperation(op);
|
|
||||||
addResponseHeader(op, "200", "Content-Type", contentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String newKey = adapterPath.isEmpty() ? pk
|
|
||||||
: (adapterPath.replaceAll("/+$", "") + (pk.startsWith("/") ? "" : "/") + pk);
|
|
||||||
if (!newKey.equals(pk)) {
|
|
||||||
paths.set(newKey, pathItem);
|
|
||||||
paths.remove(pk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return objectMapper.writeValueAsString(root);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("applyAutoRules 실패, 원본 spec 유지", e);
|
|
||||||
return specJson;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setContentExample(ObjectNode op, String sampleJson) {
|
|
||||||
if (StringUtils.isBlank(sampleJson)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonNode body = op.get("requestBody");
|
|
||||||
if (body == null || !body.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonNode content = body.get("content");
|
|
||||||
if (content == null || !content.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Iterator<String> mts = content.fieldNames();
|
|
||||||
if (!mts.hasNext()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonNode mtNode = content.get(mts.next());
|
|
||||||
if (mtNode != null && mtNode.isObject()) {
|
|
||||||
((ObjectNode) mtNode).set("example", parseJsonOrText(sampleJson));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setResponseExample(ObjectNode op, String code, String sampleJson) {
|
|
||||||
if (StringUtils.isBlank(sampleJson)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonNode resps = op.get("responses");
|
|
||||||
if (resps == null || !resps.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonNode r = resps.get(code);
|
|
||||||
if (r == null || !r.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ObjectNode content = getOrCreateObject((ObjectNode) r, "content");
|
|
||||||
ObjectNode appjson = getOrCreateObject(content, "application/json");
|
|
||||||
appjson.set("example", parseJsonOrText(sampleJson));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GW 유래 스키마 필드에 x-djb-gw 마커 부여(프론트에서 잠금 표시). */
|
|
||||||
private void markGwOnOperation(ObjectNode op) {
|
|
||||||
JsonNode body = op.get("requestBody");
|
|
||||||
if (body != null && body.isObject()) {
|
|
||||||
markGwInContent(body.get("content"));
|
|
||||||
}
|
|
||||||
JsonNode resps = op.get("responses");
|
|
||||||
if (resps != null && resps.isObject()) {
|
|
||||||
List<String> codes = new ArrayList<>();
|
|
||||||
Iterator<String> it = resps.fieldNames();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
codes.add(it.next());
|
|
||||||
}
|
|
||||||
for (String c : codes) {
|
|
||||||
JsonNode r = resps.get(c);
|
|
||||||
if (r != null && r.isObject()) {
|
|
||||||
markGwInContent(r.get("content"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markGwInContent(JsonNode content) {
|
|
||||||
if (content == null || !content.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<String> mts = new ArrayList<>();
|
|
||||||
Iterator<String> it = content.fieldNames();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
mts.add(it.next());
|
|
||||||
}
|
|
||||||
for (String mt : mts) {
|
|
||||||
JsonNode m = content.get(mt);
|
|
||||||
if (m != null && m.isObject()) {
|
|
||||||
markGwSchema(m.get("schema"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markGwSchema(JsonNode schema) {
|
|
||||||
if (schema == null || !schema.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ObjectNode s = (ObjectNode) schema;
|
|
||||||
JsonNode props = s.get("properties");
|
|
||||||
if (props != null && props.isObject()) {
|
|
||||||
ArrayNode required = (s.get("required") != null && s.get("required").isArray())
|
|
||||||
? (ArrayNode) s.get("required") : objectMapper.createArrayNode();
|
|
||||||
java.util.Set<String> existing = new java.util.HashSet<>();
|
|
||||||
required.forEach(n -> existing.add(n.asText()));
|
|
||||||
List<String> keys = new ArrayList<>();
|
|
||||||
Iterator<String> it = props.fieldNames();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
keys.add(it.next());
|
|
||||||
}
|
|
||||||
for (String k : keys) {
|
|
||||||
JsonNode p = props.get(k);
|
|
||||||
if (p != null && p.isObject()) {
|
|
||||||
((ObjectNode) p).put("x-djb-gw", true);
|
|
||||||
if (!existing.contains(k)) { // GW 필드는 기본 '필수'
|
|
||||||
required.add(k);
|
|
||||||
existing.add(k);
|
|
||||||
}
|
|
||||||
markGwSchema(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (required.size() > 0) {
|
|
||||||
s.set("required", required);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JsonNode items = s.get("items");
|
|
||||||
if (items != null && items.isObject()) {
|
|
||||||
markGwSchema(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 응답 헤더 미리 등록(중복 시 스킵). */
|
|
||||||
private void addResponseHeader(ObjectNode op, String code, String headerName, String example) {
|
|
||||||
JsonNode resps = op.get("responses");
|
|
||||||
if (resps == null || !resps.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonNode r = resps.get(code);
|
|
||||||
if (r == null || !r.isObject()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ObjectNode headers = getOrCreateObject((ObjectNode) r, "headers");
|
|
||||||
if (headers.has(headerName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ObjectNode h = objectMapper.createObjectNode();
|
|
||||||
h.put("description", headerName);
|
|
||||||
ObjectNode sc = objectMapper.createObjectNode();
|
|
||||||
sc.put("type", "string");
|
|
||||||
h.set("schema", sc);
|
|
||||||
h.put("example", example);
|
|
||||||
headers.set(headerName, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode parseJsonOrText(String s) {
|
|
||||||
try {
|
|
||||||
return objectMapper.readTree(s);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return TextNode.valueOf(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ObjectNode getOrCreateObject(ObjectNode parent, String field) {
|
|
||||||
JsonNode node = parent.get(field);
|
|
||||||
if (node != null && node.isObject()) {
|
|
||||||
return (ObjectNode) node;
|
|
||||||
}
|
|
||||||
ObjectNode created = objectMapper.createObjectNode();
|
|
||||||
parent.set(field, created);
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void putIfNotNull(Map<String, String> map, String key, String value) {
|
private void putIfNotNull(Map<String, String> map, String key, String value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map.put(key, value);
|
map.put(key, value);
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ public class ApiInterfaceUI {
|
|||||||
private String apiFullPath;
|
private String apiFullPath;
|
||||||
private String bzwksvckeyname;
|
private String bzwksvckeyname;
|
||||||
private String statusCode;
|
private String statusCode;
|
||||||
|
private String specStatus;
|
||||||
|
private List<String> publishedGroupNames;
|
||||||
|
|
||||||
//ASYNC-SYNC 용
|
//ASYNC-SYNC 용
|
||||||
private String inboundResponseHttpMethod;
|
private String inboundResponseHttpMethod;
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<configuration scan="true" scanPeriod="10 seconds">
|
||||||
|
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
|
||||||
|
|
||||||
|
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||||
|
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||||
|
<property name="LOG_HOME" value="/work/eactive/djb-eapim/workspace/eapim-admin/logs" />
|
||||||
|
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||||
|
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
||||||
|
|
||||||
|
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="STDOUT"
|
||||||
|
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/${PREFIX}_stdout.log</file>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${LOG_HOME}/${PREFIX}_stdout.log-%d{yyyy-MM-dd}.%i
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
<maxHistory>10</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="ACCESS_APPENDER"
|
||||||
|
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/${PREFIX}_access.log</file>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${LOG_HOME}/${PREFIX}_access.log-%d{yyyy-MM-dd}.%i
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<appender name="SMS_APPENDER"
|
||||||
|
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/sms.log</file>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${LOG_HOME}/sms.log-%d{yyyy-MM-dd}.%i
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
<maxHistory>10</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<appender name="QUERY_APPENDER"
|
||||||
|
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/${PREFIX}_query.log</file>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${LOG_HOME}/${PREFIX}_query.log-%d{yyyy-MM-dd}.%i
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
<maxHistory>10</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<appender name="DYNAMIC_APPENDER"
|
||||||
|
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/${PREFIX}_dynamic.log</file>
|
||||||
|
<rollingPolicy
|
||||||
|
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${LOG_HOME}/${PREFIX}_dynamic.log-%d{yyyy-MM-dd}.%i
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
<maxHistory>10</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<appender name="HIBERNATE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/${PREFIX}_hibernate.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_HOME}/${PREFIX}_hibernate.log-%d{yyyy-MM-dd}.%i</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
<maxHistory>10</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<appender name="HOTSWAP_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_HOME}/${PREFIX}_hotswap.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_HOME}/${PREFIX}_hotswap.log-%d{yyyy-MM-dd}.%i</fileNamePattern>
|
||||||
|
<maxFileSize>500MB</maxFileSize>
|
||||||
|
<maxHistory>10</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.eactive.eai.rms.onl.server" level="ERROR" />
|
||||||
|
<logger name="com.eactive.eai.rms.onl.dashboard" level="ERROR" />
|
||||||
|
<logger name="com.eactive.eai.rms.common.acl.user.BizDao" level="ERROR" />
|
||||||
|
<logger name="com.eactive.eai.rms.common.acl.user.BizDao" level="ERROR" />
|
||||||
|
<logger name="com.eactive.eai.rms.ext.djb.job" level="DEBUG" />
|
||||||
|
<logger name="com.eactive.eai.rms.ext.djb.apistatus" level="DEBUG" />
|
||||||
|
|
||||||
|
<logger name="org.springframework.orm.jpa.JpaTransactionManager" level="ERROR" additivity="false" />
|
||||||
|
|
||||||
|
<logger name="org.springframework" level="INFO" />
|
||||||
|
<logger name="net.sf.ehcache" level="INFO" />
|
||||||
|
|
||||||
|
|
||||||
|
<logger name="ACCESS_LOGGER" level="DEBUG" >
|
||||||
|
<appender-ref ref="ACCESS_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<logger name="SMS_LOGGER" level="DEBUG" >
|
||||||
|
<appender-ref ref="SMS_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<logger name="DYNAMIC_LOGGER" level="DEBUG" >
|
||||||
|
<appender-ref ref="DYNAMIC_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- QUERY_APPENDER start -->
|
||||||
|
<!-- Hibernate SQL & 파라미터 바인딩 -->
|
||||||
|
<logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="QUERY_APPENDER" />
|
||||||
|
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
|
||||||
|
<appender-ref ref="QUERY_APPENDER" />
|
||||||
|
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="TRACE" additivity="false">
|
||||||
|
<appender-ref ref="QUERY_APPENDER" />
|
||||||
|
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<!-- Hibernate 일반 로그 (DDL/init/connection 등) -->
|
||||||
|
<logger name="org.hibernate" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="HIBERNATE_APPENDER" />
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- iBATIS 2.3.4 (commons-logging 또는 log4j-over-slf4j 통해 SLF4J 도달) -->
|
||||||
|
<logger name="com.ibatis" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="QUERY_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<logger name="com.eactive.eai.rms.common.base.SchemaIdConfiguredSqlMapTemplate" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="QUERY_APPENDER" />
|
||||||
|
</logger>
|
||||||
|
<!-- QUERY_APPENDER end -->
|
||||||
|
|
||||||
|
<!-- HotswapAgent는 자체 logger를 사용하므로 logback에 안 잡힘. hotswap-agent.properties의 LOGFILE로 직접 파일 출력 -->
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<level value="INFO" />
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.eactive.eai.rms.data.entity.onl.apim.apispec;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.cfg.Configuration;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
|
||||||
|
|
||||||
|
class ApiSpecStatusRepositoryTest {
|
||||||
|
private static SessionFactory sessionFactory;
|
||||||
|
private static EntityManager entityManager;
|
||||||
|
private static ApiSpecStatusRepository repository;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUp() {
|
||||||
|
sessionFactory = new Configuration()
|
||||||
|
.addAnnotatedClass(ApiSpecInfo.class)
|
||||||
|
.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect")
|
||||||
|
.setProperty("hibernate.connection.driver_class", "org.h2.Driver")
|
||||||
|
.setProperty("hibernate.connection.url", "jdbc:h2:mem:apiSpecStatus;DB_CLOSE_DELAY=-1")
|
||||||
|
.setProperty("hibernate.hbm2ddl.auto", "create-drop")
|
||||||
|
.buildSessionFactory();
|
||||||
|
entityManager = sessionFactory.createEntityManager();
|
||||||
|
repository = new JpaRepositoryFactory(entityManager).getRepository(ApiSpecStatusRepository.class);
|
||||||
|
entityManager.getTransaction().begin();
|
||||||
|
save("PUBLISHED1", "Y");
|
||||||
|
save("REGISTERED1", "N");
|
||||||
|
save("LEGACY2", null);
|
||||||
|
entityManager.getTransaction().commit();
|
||||||
|
entityManager.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save(String apiId, String displayYn) {
|
||||||
|
ApiSpecInfo spec = new ApiSpecInfo();
|
||||||
|
spec.setApiId(apiId);
|
||||||
|
spec.setDisplayYn(displayYn);
|
||||||
|
entityManager.persist(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDown() {
|
||||||
|
if (entityManager != null) entityManager.close();
|
||||||
|
if (sessionFactory != null) sessionFactory.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void publishedIncludesOnlyDisplayedSpecs() {
|
||||||
|
assertEquals(Arrays.asList("PUBLISHED1"), repository.findApiIdsByStatus("PUBLISHED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registeredIncludesNonDisplayedAndLegacyNullSpecs() {
|
||||||
|
assertEquals(new HashSet<>(Arrays.asList("REGISTERED1", "LEGACY2")),
|
||||||
|
new HashSet<>(repository.findApiIdsByStatus("REGISTERED")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void statusProjectionIncludesOnlyRequestedExistingSpecs() {
|
||||||
|
Map<String, String> statuses = repository.findStatusesByApiIds(
|
||||||
|
Arrays.asList("PUBLISHED1", "LEGACY2", "MISSING2")).stream()
|
||||||
|
.collect(Collectors.toMap(ApiSpecStatusRepository.SpecStatus::getApiId,
|
||||||
|
spec -> "Y".equals(spec.getDisplayYn()) ? "PUBLISHED" : "REGISTERED"));
|
||||||
|
assertEquals(2, statuses.size());
|
||||||
|
assertEquals("PUBLISHED", statuses.get("PUBLISHED1"));
|
||||||
|
assertEquals("REGISTERED", statuses.get("LEGACY2"));
|
||||||
|
assertEquals("UNREGISTERED", statuses.getOrDefault("MISSING2", "UNREGISTERED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingApiHasNoSpecStatus() {
|
||||||
|
assertTrue(repository.findStatusesByApiIds(Arrays.asList("MISSING2")).isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user