67 Commits

Author SHA1 Message Date
eastargh 314eb87ea4 API 통계 footer에 합계 표시
eapim-admin CI / build (push) Has been cancelled
2026-08-24 13:01:43 +09:00
eastargh a6e88f2281 IIM연동 오류 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-24 09:41:23 +09:00
eastargh 38f89fcd7a IIM연동 이력 조회
eapim-admin CI / build (push) Has been cancelled
2026-08-21 17:25:17 +09:00
eastargh 48073d5d91 메뉴명 변경
eapim-admin CI / build (push) Has been cancelled
2026-08-21 15:08:40 +09:00
eastargh 79653a2887 IIM 연동이력 조회 화면 2026-08-21 15:05:18 +09:00
eastargh 97d6489bf7 IIM 연동시 interface 로그 저장
eapim-admin CI / build (push) Has been cancelled
2026-08-21 13:59:17 +09:00
eastargh 1bf3a950e3 IIM연동시 dev가 삭제된경우, 추가로 만들지 않는다
eapim-admin CI / build (push) Has been cancelled
2026-08-20 14:03:47 +09:00
eastargh 12e4213fa1 소스취약점 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-20 10:48:38 +09:00
eastargh e60d5443a3 스케쥴러 clustered 설정 변경
eapim-admin CI / build (push) Has been cancelled
2026-08-19 15:19:55 +09:00
Rinjae a15fa5ad66 Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled
2026-08-19 14:31:19 +09:00
Rinjae 7b8646ab94 자동 탐지 미게시 포함 설정 추가
- 설정 키 확인 로직 추가 및 미게시 인터페이스 처리 적용
- 게시/미게시 API 필터링 로직 분리 및 오류 처리 강화
2026-08-19 14:31:12 +09:00
eastargh 26f85b6f11 동시실행 방지 @DisallowConcurrentExecution 추가
eapim-admin CI / build (push) Has been cancelled
2026-08-19 14:15:48 +09:00
eastargh de6f7e5ff2 스케쥴러 CLUSTERED config 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-19 13:37:17 +09:00
eastargh e2206aedc4 메세지발송내역 - apistatus_changed 마스킹 예외 적용
eapim-admin CI / build (push) Has been cancelled
2026-08-19 11:11:28 +09:00
eastargh 2e30239a90 UMS messenger api-monitor 발송여부 체크
eapim-admin CI / build (push) Has been cancelled
2026-08-18 15:03:45 +09:00
eastargh e28d6e54c5 api관리 검색조건 추가
eapim-admin CI / build (push) Has been cancelled
2026-08-18 14:29:47 +09:00
eastargh eb5e289a4e 헤더복사 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-18 13:52:10 +09:00
eastargh ca9c3240bf API상태변화 직원 스윙챗발송여부 환경변수 처리
eapim-admin CI / build (push) Has been cancelled
2026-08-18 09:26:12 +09:00
eastargh 289f8e2f2f 인터페이스 배포버튼
eapim-admin CI / build (push) Has been cancelled
2026-08-15 10:47:45 +09:00
eastargh a0a1954a58 damo-manager lib에 포함 & log-path 환경변수 설정 2026-08-15 10:47:22 +09:00
eastargh 0149da372e 인터페이스관리 - 배포 버튼(uploadfull) 추가
eapim-admin CI / build (push) Has been cancelled
2026-08-14 18:04:19 +09:00
eastargh 3bb0325bf7 대시보드 api 검색조건 추가
eapim-admin CI / build (push) Has been cancelled
2026-08-14 17:30:40 +09:00
eastargh 579cb61a75 @DisallowConcurrentExecution 제거
eapim-admin CI / build (push) Has been cancelled
2026-08-14 17:04:03 +09:00
eastargh 6c67e3f1d8 ... 2026-08-14 17:02:41 +09:00
eastargh a19f65a660 불필요한 로그 제거 2026-08-14 17:01:40 +09:00
eastargh 0e616bc995 UMS 발송 핸드폰번호 null 체크
eapim-admin CI / build (push) Has been cancelled
2026-08-14 16:59:19 +09:00
eastargh 7ac9c05f62 UMS 스윙챗 설정 변경
eapim-admin CI / build (push) Has been cancelled
2026-08-14 10:02:27 +09:00
eastargh 7c9f87db73 팝업오픈시 매핑코드 전달
eapim-admin CI / build (push) Has been cancelled
2026-08-13 17:28:32 +09:00
eastargh 8448d99fe8 Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin.git 2026-08-13 17:28:14 +09:00
eastargh ec9cc19821 검색조건 유지 2026-08-13 17:28:06 +09:00
Rinjae c2d6eb4c34 레거시 암호화 컨트롤러 제거 - 내부 API 토큰 초기화 로직 추가 - 정규식 성능 최적화
eapim-admin CI / build (push) Has been cancelled
2026-08-13 16:52:04 +09:00
Rinjae e364a35b66 - 소수 자릿수 처리 개선 - DECIMAL/BIGDECIMAL 정수/숫자 구분
eapim-admin CI / build (push) Has been cancelled
- 샘플 생성 로직 리팩토링 - 길이 및 소수 자릿수 기준 상세화
- 공통 스키마 처리 함수 추가 - 코드 중복 제거 및 가독성 향상
2026-08-13 14:42:44 +09:00
Rinjae 760000070d APP 승인 시 GW 반영 로직 개선
eapim-admin CI / build (push) Has been cancelled
- GwClientSyncService 분리 및 트랜잭션 관리 강화
- 승인 화면에 GW 반영 필수 정보 노출 추가
- JSP 동적 행 노출 및 승인 데이터 검증 로직 적용
2026-08-13 14:37:31 +09:00
eastargh 71e433a2a5 IIM연동시 삭제 오류 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-13 10:50:42 +09:00
eastargh 5830031f32 전문레이아웃 매핑 화면 오픈시 validation
eapim-admin CI / build (push) Has been cancelled
2026-08-13 10:35:26 +09:00
eastargh c7be9760a0 IIM 연동시 삭제 오류 수정 2026-08-13 10:34:34 +09:00
eastargh ee0b882608 변환명령에 함수가 적용된 경우, 복제시 함수명 잘리는 버그 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-12 17:46:10 +09:00
eastargh 2cdd321c39 전문레이아웃매핑 변환명령에 아이템 멀티선택 콤보 추가
eapim-admin CI / build (push) Has been cancelled
2026-08-12 17:04:04 +09:00
Rinjae d1eecb4ee7 v1 API 스펙 버튼 노출 설정 추가
eapim-admin CI / build (push) Has been cancelled
- PortalProperty 기반 노출 여부 제어 로직 추가
- JSP 뷰모델에 apiSpecV1Enabled 속성 연동
2026-08-11 19:14:28 +09:00
Rinjae 91a156c6b0 - Swagger spec 서버 주소 GW 기준 치환 통합 - UI/다운로드 동일 기준
eapim-admin CI / build (push) Has been cancelled
- ApiTesterFilter: gw 호출 시 base-url 재조립 처리 추가
- admin 스펙 저장 시 실주소 고정 제거 - 재치환 유연성 확보
2026-08-11 19:00:18 +09:00
Rinjae c19c12df70 응답유형별 서버 실주소 처리 개선
eapim-admin CI / build (push) Has been cancelled
- 저장본에 sentinel 유지 및 편집기 사용 시 실주소로 치환
- 응답유형별 서버 실주소 계산 유틸 함수 추가
2026-08-11 16:56:33 +09:00
eastargh 347cca2d7c IIM연동시 개발 인터페이스를 추가로 만들어 준다
eapim-admin CI / build (push) Has been cancelled
2026-08-11 15:17:19 +09:00
eastargh d8a1ca84e2 전문레이아웃 매핑 선연결 제외 2026-08-11 15:15:58 +09:00
Rinjae bf4f953d56 변경/해지 신청 개선 및 사전검사 추가
eapim-admin CI / build (push) Has been cancelled
- 변경/해지 상태 표시 및 취소 기능 추가
- 해지 신청 사전검사 및 경고 메시지 처리 구현
- 관리자 승인 이전 허용되는 취소 정책 지원
2026-08-11 14:15:34 +09:00
eastargh 814571eeba rms.webhook.enabled 프라퍼티에 따라 발송
eapim-admin CI / build (push) Has been cancelled
2026-08-11 09:37:14 +09:00
eastargh 13850ba329 IIM연동 로그 저장시 시퀀스 적용
eapim-admin CI / build (push) Has been cancelled
2026-08-10 15:55:55 +09:00
eastargh 04b4d415a9 인터페이스 미확인 오류 로그- 업무데이타 내용 복호화
eapim-admin CI / build (push) Has been cancelled
2026-08-10 13:40:19 +09:00
eastargh 38902cf6b3 로그 조회 GUID 입력시 다른 조건 무시
eapim-admin CI / build (push) Has been cancelled
2026-08-10 13:39:46 +09:00
eastargh 7ebbd7d9e4 로그조회시 GUID입력시 다른 조건 무시
eapim-admin CI / build (push) Has been cancelled
2026-08-10 10:42:39 +09:00
eastargh f59de66d2c UMS email 설정 변경
eapim-admin CI / build (push) Has been cancelled
2026-08-10 09:23:50 +09:00
eastargh c179e50047 Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin.git
eapim-admin CI / build (push) Has been cancelled
2026-08-10 09:14:16 +09:00
eastargh 39f0baf5ef 인터페이스 세부설정관리 - 그리드/검색조건 재구성 2026-08-10 09:14:08 +09:00
Rinjae 08354837b1 OpenAPI Spec·포탈 노출 필터 추가 - EAIMessage 검색 조건 추가 - API Group 팝업 검색 개선 - JSP 체크박스 연동 처리
eapim-admin CI / build (push) Has been cancelled
2026-08-07 13:40:49 +09:00
Rinjae a31b991e59 API 판정 로직 다중 스키마 지원 - 게시 조건 분리 처리
eapim-admin CI / build (push) Has been cancelled
- API 그룹/스펙 조건 분리 및 추가 Repository 적용
- ORA-00942 방지 위해 EMSAPP 조회 로직 수정
2026-08-06 17:59:38 +09:00
Rinjae 615e56687a Merge remote-tracking branch 'origin/master'
eapim-admin CI / build (push) Has been cancelled
2026-08-06 17:05:50 +09:00
Rinjae ba1161be7b Quartz 스케줄러 상태 JSP 추가
- Quartz 스케줄러 상태 표시용 quartzStatus.jsp 신규 작성
- 스케줄러/잡/트리거 정보 표시 및 자동 새로고침 기능 구현
2026-08-06 16:57:39 +09:00
eastargh 7430b03323 다시 적용
eapim-admin CI / build (push) Has been cancelled
2026-08-06 11:33:52 +09:00
eastargh 5b05489924 일단 원래데로
eapim-admin CI / build (push) Has been cancelled
2026-08-06 11:05:45 +09:00
eastargh 3c386b780b Merge branch 'master' of https://git.eactive.synology.me:8090/eapim/djbank/eapim-admin.git
eapim-admin CI / build (push) Has been cancelled
2026-08-06 10:47:01 +09:00
eastargh f928a0b1e4 전문매핑 단일 연결/해제 버튼 추가 2026-08-06 10:46:53 +09:00
Rinjae dc9e737aa9 IP 매칭 유틸리티 추가 - CIDR/와일드카드 포함(#194).
eapim-admin CI / build (push) Has been cancelled
포탈 캐시 리로드 다중 서버 지원 - UI 개선/동시 호출 추가.
내부 API 유틸/테스트 및 JSP UI 연계 최적화.
2026-08-06 09:59:23 +09:00
eastargh db57e95233 이벤트유형 CONTROL_ > CHECK_ 로 변경
eapim-admin CI / build (push) Has been cancelled
2026-08-06 09:15:57 +09:00
Rinjae 643e7fcdf2 damo-manager.jar 라이브러리 업데이트
eapim-admin CI / build (push) Has been cancelled
- eapim-admin, eapim-online, elink-portal 공통 반영
2026-08-05 17:46:55 +09:00
Rinjae 36994a57b6 속성 관리 화면 수정
eapim-admin CI / build (push) Has been cancelled
- 다중라인 값 편집 및 줄바꿈 지원 PRPTY2VAL 속성 수정
- prpty2Val 입력 필드 텍스트 영역으로 변경 및 스타일 추가
- jQuery 선택자 간소화 `<input>` → `<textarea>` for consistency
2026-08-05 15:23:46 +09:00
Rinjae 4a087e576b Merge commit '9f22d4fa4d6720aabb06a5541449ebb120397987'
eapim-admin CI / build (push) Has been cancelled
2026-08-05 14:22:34 +09:00
eastargh 9f22d4fa4d 로그상세에서 정렬클릭시 지수형 풀리지 않도록
eapim-admin CI / build (push) Has been cancelled
2026-08-05 11:36:51 +09:00
eastargh 66c01029cd UMS 설정 수정
eapim-admin CI / build (push) Has been cancelled
2026-08-05 11:12:46 +09:00
86 changed files with 4792 additions and 2073 deletions
+45 -33
View File
@@ -191,25 +191,12 @@
}
});
// 경로(op.path.value)에는 어댑터경로가 이미 baking 됨(백엔드 applyAutoRules). 서버는 응답유형별 호스트만 붙인다.
// - gw : djb.gateway.base-url (PortalProperty) → [gw호스트 + 어댑터경로 + 경로]
// - mock : Mock Server URL(전체 URL) → [mock URL 그대로] (어댑터/오퍼레이션 경로 미부착)
// - 그 외 : 호스트 없음 → [어댑터경로 + 경로]
var _rt = (d.portal && d.portal.responseType) || 'sample';
var _host;
var _pathKey = op.path.value;
if (_rt === 'gw') {
_host = (window.DJB_CTX && window.DJB_CTX.gwAddress) || 'http://127.0.0.1:39310';
} else if (_rt === 'mock') {
// Mock 은 전체 호출 URL. origin=서버, pathname(+query)=경로로 분해해 경로가 뒤에 덧붙지 않도록 한다.
var _mk = splitFullUrl((d.portal && d.portal.mockUrl) || '');
_host = _mk.origin;
_pathKey = _mk.path;
} else {
_host = '';
}
if (_host) spec.servers = [{ url: _host }];
spec.paths[_pathKey] = { [op.method.value.toLowerCase()]: operation };
// spec 의 서버는 응답유형과 무관하게 항상 GW 주소로 고정한다 — spec 은 "이 API 를 실제로 어디로
// 호출하는가"의 공개 계약이고, 그 기준은 게이트웨이다. sample/mock 은 테스트베드 동작(포탈
// ApiTesterFilter 가 응답유형에 따라 sampleResponse 반환 / mockUrl forward)에서만 갈린다.
// 경로(op.path.value)에는 어댑터경로가 이미 baking 됨(백엔드 applyAutoRules).
spec.servers = [{ url: gatewayAddress() }];
spec.paths[op.path.value] = { [op.method.value.toLowerCase()]: operation };
// 보안 스킴
d.securitySchemes.forEach(s => {
@@ -237,6 +224,8 @@
const s = { type: row.type.value };
if (row.format && row.format.value) s.format = row.format.value;
if (row.maxLength && row.maxLength.value) s.maxLength = Number(row.maxLength.value);
// 소수 자릿수(GW 레이아웃 유래) 보존 — 예제값 생성 시 '.' 포함 길이 계산에 사용
if (row.decimals && row.decimals.value !== '' && row.decimals.value != null) s['x-djb-decimal'] = Number(row.decimals.value);
if (row.pattern && row.pattern.value) s.pattern = row.pattern.value;
if (row.description && row.description.value) s.description = row.description.value;
if (row.example && row.example.value !== '') s.example = coerceExample(row.type.value, row.example.value);
@@ -981,6 +970,11 @@
}
}
// spec servers 에 넣을 게이트웨이 주소(단일 기준). 응답유형과 무관하게 항상 이 값을 쓴다.
function gatewayAddress() {
return (window.DJB_CTX && window.DJB_CTX.gwAddress) || 'http://127.0.0.1:39310';
}
// 전체 URL → { origin, path }. Mock Server URL 을 서버+경로로 분해(경로를 별도로 덧붙이지 않기 위함).
function splitFullUrl(u) {
u = (u || '').trim();
@@ -1474,6 +1468,7 @@
required: { value: false, locked: false },
format: { value: '', locked: false },
maxLength: { value: '', locked: false },
decimals: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '', locked: false },
@@ -1488,6 +1483,7 @@
required: { value: false, locked: false },
format: { value: '', locked: false },
maxLength: { value: '', locked: false },
decimals: { value: '', locked: false },
pattern: { value: '', locked: false },
description: { value: '', locked: false },
example: { value: '', locked: false }
@@ -1741,6 +1737,7 @@
var row = {
name: wrap(name, gw), type: wrap(t, gw), required: wrap(!!required, false),
format: wrap(p.format || ''), maxLength: wrap(p.maxLength == null ? '' : p.maxLength),
decimals: wrap(p['x-djb-decimal'] == null ? '' : p['x-djb-decimal']),
pattern: wrap(p.pattern || ''), description: wrap(p.description || ''),
example: wrap(p.example == null ? '' : p.example), children: null
};
@@ -1790,7 +1787,7 @@
};
d.parameters = (op.parameters || []).map(function (p) {
var s = p.schema || {};
return { in: p.in || 'query', name: wrap(p.name), type: wrap(s.type || 'string'), required: wrap(!!p.required), format: wrap(s.format || ''), maxLength: wrap(s.maxLength == null ? '' : s.maxLength), pattern: wrap(s.pattern || ''), description: wrap(p.description || ''), example: wrap(p.example == null ? '' : p.example) };
return { in: p.in || 'query', name: wrap(p.name), type: wrap(s.type || 'string'), required: wrap(!!p.required), format: wrap(s.format || ''), maxLength: wrap(s.maxLength == null ? '' : s.maxLength), decimals: wrap(s['x-djb-decimal'] == null ? '' : s['x-djb-decimal']), pattern: wrap(s.pattern || ''), description: wrap(p.description || ''), example: wrap(p.example == null ? '' : p.example) };
});
var rb = op.requestBody || {}; var rbc = rb.content || {}; var mt = Object.keys(rbc)[0] || 'application/json';
d.requestBody = { mediaType: mt, required: wrap(!!rb.required), schema: schemaToRows((rbc[mt] || {}).schema) };
@@ -1835,19 +1832,34 @@
// 스키마 기반 샘플 JSON 생성 (자동생성 예제)
function _coerce(t, v) { if (t === 'integer') return parseInt(v, 10) || 0; if (t === 'number') return parseFloat(v) || 0; if (t === 'boolean') return v === true || v === 'true'; return v; }
// 기본 예제값: string→sample_명(길이 지정 시 맞춰 자름), integer→123456, number→123456.123, boolean→false
// - 숫자는 길이 지정 시 정수부를 그 길이에 맞춰 자름(실수는 소수 .123 유지)
function _defScalar(t, name, maxLen) {
var ml = parseInt(maxLen, 10);
if (t === 'integer') {
var iv = '123456';
if (ml && ml > 0 && iv.length > ml) iv = iv.substring(0, ml);
return parseInt(iv, 10);
// 자릿수 문자열: start 위치부터 1~9 를 순환('123456789123...')
function _digits(start, count) { var s = ''; for (var i = 0; i < count; i++) s += String(((start + i) % 9) + 1); return s; }
// 숫자 예제값. 길이는 소수점(.)까지 포함한 문자열 전체 길이로 계산한다.
// 예) 길이 9 / 소수 5 → 123.45678 (정수부 3 + '.' 1 + 소수부 5 = 9자)
// - 길이가 소수부+2 보다 짧으면 정수부 1자리를 확보하고 소수부를 줄인다.
// - 길이 미지정이면 정수부 6자리(기존 기본값)를 사용한다.
function _numSample(maxLen, dec) {
var ml = parseInt(maxLen, 10) || 0, d = parseInt(dec, 10) || 0;
if (d <= 0) return _digits(0, ml > 0 ? Math.min(ml, 6) : 6);
var il;
if (ml > 0) {
il = ml - d - 1;
if (il < 1) { il = 1; d = Math.max(1, ml - 2); }
} else {
il = 6;
}
return _digits(0, il) + '.' + _digits(il, d);
}
// 기본 예제값: string→sample_명(길이 지정 시 맞춰 자름), integer→123456, number→123.45678, boolean→false
// - 숫자는 지정 길이를 넘지 않도록 자릿수를 맞춘다(소수는 '.' 포함 길이 기준).
// - number 인데 소수 자릿수 정보가 없으면 3자리로 가정(소수 0자리는 백엔드가 integer 로 매핑).
function _defScalar(t, name, maxLen, decimals) {
var ml = parseInt(maxLen, 10);
if (t === 'integer') return parseInt(_numSample(ml, 0), 10);
if (t === 'number') {
var ip = '123456';
if (ml && ml > 0 && ip.length > ml) ip = ip.substring(0, ml);
return parseFloat(ip + '.123');
var d = parseInt(decimals, 10);
if (!(d > 0)) d = 3;
return parseFloat(_numSample(ml, d));
}
if (t === 'boolean') return false;
var v = 'sample_' + (name || '');
@@ -1859,7 +1871,7 @@
if (t === 'object') return _sampleObj(row.children || []);
if (t === 'array') { var it = (row.itemsType && row.itemsType.value) || 'string'; return [it === 'object' ? _sampleObj(row.children || []) : _defScalar(it, (row.name && row.name.value) || 'item', '')]; }
if (ex !== undefined && ex !== '') return _coerce(t, ex);
return _defScalar(t, (row.name && row.name.value) || '', row.maxLength && row.maxLength.value);
return _defScalar(t, (row.name && row.name.value) || '', row.maxLength && row.maxLength.value, row.decimals && row.decimals.value);
}
function _sampleObj(rows) { var o = {}; (rows || []).forEach(function (r) { if (r && r.name && r.name.value) o[r.name.value] = _sampleVal(r); }); return o; }
function ensureExamplesFromSchema(force) {
@@ -1905,7 +1917,7 @@
if (r.children && r.children.length) fillSchemaExamples(r.children);
} else {
r.example = r.example || { value: '', locked: false };
r.example.value = String(_defScalar(t, r.name.value || '', r.maxLength && r.maxLength.value));
r.example.value = String(_defScalar(t, r.name.value || '', r.maxLength && r.maxLength.value, r.decimals && r.decimals.value));
}
});
}
+15 -11
View File
@@ -45,9 +45,13 @@ $(document).ready(function() {
}
});
//검색조건 달력
$("#startDatepicker").datepicker();
$("#endDatepicker").datepicker();
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
gridPostData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val() + $("input[name=searchStartHHMM]").val();
gridPostData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val() + $("input[name=searchEndHHMM]").val();
gridPostData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchStartHHMM]").val();
gridPostData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchEndHHMM]").val();
$('#grid').jqGrid({
datatype:"json",
mtype: 'POST',
@@ -107,8 +111,8 @@ $(document).ready(function() {
$("#btn_search").click(function(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
postData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val() + $("input[name=searchStartHHMM]").val();
postData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val() + $("input[name=searchEndHHMM]").val();
postData['searchStartDateTime'] = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchStartHHMM]").val();
postData['searchEndDateTime'] = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "") + $("input[name=searchEndHHMM]").val();
$("#grid").setGridParam({ postData: postData ,page : "1" }).trigger("reloadGrid");
});
@@ -154,11 +158,11 @@ function getEndTime(){
<tr>
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.logamndHMS") %></th>
<td>
<input type="text" name="searchStartYYYYMMDD" value="" size="10" style="width:100px; border:1px solid #ebebec;">
<input type="text" name="searchStartHHMM" value="" size="8" style="width:100px; border:1px solid #ebebec;">
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" value="${param.searchStartYYYYMMDD}" readonly="readonly" size="10" style="width:100px; border:1px solid #ebebec;">
<input type="text" name="searchStartHHMM" value="${param.searchStartHHMM}" size="8" style="width:100px; border:1px solid #ebebec;">
~
<input type="text" name="searchEndYYYYMMDD" value="" size="10" style="width:100px; border:1px solid #ebebec;">
<input type="text" name="searchEndHHMM" value="" size="8" style="width:100px; border:1px solid #ebebec;">
<input type="text" name="searchEndYYYYMMDD" id="endDatepicker" value="${param.searchEndYYYYMMDD}" readonly="readonly" size="10" style="width:100px; border:1px solid #ebebec;">
<input type="text" name="searchEndHHMM" value="${param.searchEndHHMM}" size="8" style="width:100px; border:1px solid #ebebec;">
<input type="hidden" name="searchStartDateTime" />
<input type="hidden" name="searchEndDateTime" />
</td>
@@ -168,9 +172,9 @@ function getEndTime(){
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.systemCode") %></th>
<td><div class = "select-style">
<select id="searchSystemCode" name="searchSystemCode">
<option value=""><%= localeMessage.getString("combo.all")%></option>
<option value="" <c:if test="${empty param.searchSystemCode}">selected</c:if>><%= localeMessage.getString("combo.all")%></option>
<c:forEach items="${serviceTypeCombos }" var="serviceTypeCombo">
<option value="${serviceTypeCombo.code }">${serviceTypeCombo.codename }</option>
<option value="${serviceTypeCombo.code }" <c:if test="${serviceTypeCombo.code == param.searchSystemCode}">selected</c:if>>${serviceTypeCombo.codename }</option>
</c:forEach>
</select>
</div>
@@ -180,7 +184,7 @@ function getEndTime(){
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.logTypeText") %></th>
<td>
<input type="text" value="" name="searchLogTypeText" checked="checked"/>
<input type="text" value="${param.searchLogTypeText}" name="searchLogTypeText" checked="checked"/>
</td>
<th style="width:180px;"><%= localeMessage.getString("auditLogMan.remoteAddress") %></th>
<td><input type="text" name="searchRemoteAddress" value="${param.searchRemoteAddress}" style="width:100%"></td>
@@ -31,6 +31,8 @@ function init(){
new makeOptions("CODE","NAME").setObj($("select[name=searchJobInst]")).setNoValueInclude(true).setNoValue("",
"<%= localeMessage.getString("combo.all")%>").setData(json.instanceList).rendering();
$("select[name=searchJobUseYn]").val("Y");
putSelectFromParam();
initGrid();
@@ -0,0 +1,165 @@
<%@ page language="java" contentType="text/html; charset=utf-8" %>
<%@ page import="java.io.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
%>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript">
var url = '<c:url value="/onl/admin/rule/layoutSyncHistoryMan.json" />';
var url_view = '<c:url value="/onl/admin/rule/layoutSyncHistoryMan.view" />';
function formatPrcssRslt(cellvalue, options, rowObject) {
if (cellvalue === 'S') return '<span style="color:#1a73e8;font-weight:bold;">성공</span>';
if (cellvalue === 'F') return '<span style="color:#d93025;font-weight:bold;">실패</span>';
return cellvalue || '';
}
function init() {
$("#startDatepicker").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true});
$("#endDatepicker").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true});
var today = getToday();
$("input[name=searchStartYYYYMMDD]").val(today.substring(0, 6) + "01");
$("input[name=searchEndYYYYMMDD]").val(today);
$("input[name=searchStartDate]").val(today.substring(0, 6) + "01");
$("input[name=searchEndDate]").val(today);
// 뒤로가기 등으로 이전 검색조건 파라미터가 있으면 기본값을 덮어씀
putSelectFromParam();
buildGrid();
}
function buildGrid() {
$('#grid').jqGrid({
datatype: "json",
mtype: 'POST',
url: url,
postData: getSearchForJqgrid("cmd", "LIST"),
colNames: ['No', 'Service Name', 'Command', '레이아웃명', '연동시간', '결과', '결과메세지'],
colModel: [
{name: 'seqno', align: 'center', width: 50, sortable: false},
{name: 'serviceName', align: 'center', width: 100, sortable: false},
{name: 'command', align: 'center', width: 80, sortable: false},
{name: 'loutName', align: 'left', width: 180, sortable: false},
{name: 'recvAmndHms', align: 'center', width: 140, sortable: false},
{name: 'prcssRslt', align: 'center', width: 60, sortable: false, formatter: formatPrcssRslt},
{name: 'prcssRsltCmnt', align: 'left', width: 300, sortable: false}
],
jsonReader: {repeatitems: false},
pager: $('#pager'),
page: '${param.page}',
rowNum: '${rmsDefaultRowNum}',
autoheight: true,
height: $("#container").height(),
autowidth: true,
viewrecords: true,
rowList: eval('[${rmsDefaultRowList}]'),
ondblClickRow: function (rowId) {
var rowData = $(this).getRowData(rowId);
var seqno = rowData['seqno'];
var url2 = url_view + '?cmd=DETAIL';
url2 += '&page=' + $(this).getGridParam("page");
url2 += '&returnUrl=' + getReturnUrl();
url2 += '&menuId=' + '${param.menuId}';
url2 += '&seqno=' + seqno;
url2 += '&' + getSearchUrl();
goNav(url2);
},
loadError: function (jqXHR, textStatus, errorThrown) {
var location = '<%=request.getContextPath()%>/';
comloadError(jqXHR, textStatus, errorThrown, location);
}
});
resizeJqGridWidth('grid', 'content_middle', '1000');
}
$(document).ready(function () {
init();
$("#btn_search").click(function () {
var start = $("input[name=searchStartYYYYMMDD]").val().replace(/-/gi, "");
var end = $("input[name=searchEndYYYYMMDD]").val().replace(/-/gi, "");
if (start && end && start > end) {
alert("조회기간 종료일은 시작일보다 커야합니다.");
$("input[name=searchEndYYYYMMDD]").focus();
return false;
}
$("input[name=searchStartDate]").val(start);
$("input[name=searchEndDate]").val(end);
var postData = getSearchForJqgrid("cmd", "LIST");
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
});
$("input[name^=search]").keydown(function (key) {
if (key.keyCode == 13) {
$("#btn_search").click();
}
});
buttonControl();
});
</script>
</head>
<body>
<div class="right_box">
<div class="content_top">
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
</div><!-- end content_top -->
<div class="content_middle" id="content_middle">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
</div>
<div class="title">전문레이아웃 연동 이력<span class="tooltip">IIM에서 연동한 인터페이스 및 전문 레이아웃 이력을 조회합니다.</span></div>
<form id="ajaxForm" onsubmit="return false;">
<table class="search_condition" cellspacing=0;>
<colgroup>
<col style="width:120px;"><col style="width:260px;">
<col style="width:120px;"><col style="width:260px;">
<col style="width:120px;"><col style="width:260px;">
</colgroup>
<tbody>
<tr>
<th>레이아웃명</th>
<td><input type="text" name="searchLoutName" autocomplete="off" style="width:95%;"></td>
<th>연동시간</th>
<td>
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
~
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:100px; border:1px solid #ebebec;">
<input type="hidden" name="searchStartDate" value="">
<input type="hidden" name="searchEndDate" value="">
</th>
<th>결과</th>
<td>
<div class="select-style" style="display:inline-block; margin-left:6px;">
<select name="searchPrcssRslt">
<option value="">전체</option>
<option value="S">성공</option>
<option value="F">실패</option>
</select>
</div>
</td>
</tr>
</tbody>
</table>
</form>
<table id="grid"></table>
<div id="pager"></div>
</div><!-- end content_middle -->
</div><!-- end right_box -->
</body>
</html>
@@ -0,0 +1,141 @@
<%@ page language="java" contentType="text/html; charset=utf-8" %>
<%@ page import="java.io.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
%>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<style>
.succ-y { color:#1a73e8; font-weight:bold; }
.succ-n { color:#d93025; font-weight:bold; }
.pre-wrap { white-space:pre-wrap; word-break:break-all; height:500px; overflow: auto; line-height:1.5; }
.recvdata-toolbar { text-align:right; margin-top: 5px; margin-bottom:6px; }
</style>
<script language="javascript">
var url = '<c:url value="/onl/admin/rule/layoutSyncHistoryMan.json" />';
var key = '${param.seqno}';
var rawRecvData = '';
var isRecvDataPretty = false;
function detail() {
if (!key) return;
$.ajax({
type: "POST", url: url, dataType: "json",
data: {cmd: 'DETAIL', seqno: key},
success: function (data) {
$('#seqno').text(data.seqno != null ? data.seqno : '');
$('#serviceName').text(data.serviceName || '');
$('#command').text(data.command || '');
$('#loutName').text(data.loutName || '');
$('#recvAmndHms').text(data.recvAmndHms || '');
$('#prcssRsltCmnt').text(data.prcssRsltCmnt || '');
rawRecvData = data.recvData || '';
isRecvDataPretty = false;
$('#recvData').text(rawRecvData);
if (data.prcssRslt === 'S') {
$('#prcssRslt').html('<span class="succ-y">성공</span>');
} else if (data.prcssRslt === 'F') {
$('#prcssRslt').html('<span class="succ-n">실패</span>');
} else {
$('#prcssRslt').text(data.prcssRslt || '');
}
},
error: function (e) { alert(e.responseText); }
});
}
// JSON 지수표기(예: 1e+21) 값이 vkbeautify 처리 중 깨지지 않도록 문자열로 감쌌다가 되돌린다.
function preserveExponential(jsonString) {
var protectedJson = jsonString.replace(/:\s*([+-]?\d+\.?\d*e[+-]?\d+)/gi, ': "$1"');
var prettyJson = vkbeautify.json(protectedJson);
prettyJson = prettyJson.replace(/:\s*"([+-]?\d+\.?\d*e[+-]?\d+)"/gi, ': $1');
return prettyJson;
}
$(document).ready(function () {
var returnUrl = getReturnUrlForReturn();
buttonControl();
detail();
$('#btn_previous').click(function () { goNav(returnUrl); });
$('#btn_pretty_recvdata').click(function () {
if (!rawRecvData) return;
if (isRecvDataPretty) {
$('#recvData').text(rawRecvData);
isRecvDataPretty = false;
return;
}
try {
$('#recvData').text(preserveExponential(rawRecvData));
isRecvDataPretty = true;
} catch (e) {
alert('JSON 형식이 아니거나 정렬할 수 없습니다.');
}
});
});
</script>
</head>
<body>
<div class="right_box">
<div class="content_top">
<ul class="path"><li><a href="#">${rmsMenuPath}</a></li></ul>
</div>
<div class="content_middle">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
</div>
<div class="title" id="title">전문레이아웃 연동 이력 상세</div>
<table class="table_row" cellspacing="0">
<colgroup>
<col style="width:12%"/><col style="width:38%"/>
<col style="width:12%"/><col style="width:38%"/>
</colgroup>
<tr>
<th>No</th>
<td><span id="seqno"></span></td>
<th>Service Name</th>
<td><span id="serviceName"></span></td>
</tr>
<tr>
<th>레이아웃명</th>
<td><span id="loutName"></span></td>
<th>Command</th>
<td><span id="command"></span></td>
</tr>
<tr>
<th>연동시간</th>
<td><span id="recvAmndHms"></span></td>
<th>결과</th>
<td><span id="prcssRslt"></span></td>
</tr>
<tr>
<th>결과메세지</th>
<td colspan="3"><span id="prcssRsltCmnt"></span></td>
</tr>
<tr>
<th>수신데이타</th>
<td colspan="3">
<div class="recvdata-toolbar">
<button type="button" class="cssbtn" id="btn_pretty_recvdata"><i class="material-icons">format_align_left</i> 정렬</button>
</div>
<div id="recvData" class="pre-wrap"></div>
</td>
</tr>
</table>
</div>
</div>
</body>
</html>
@@ -45,6 +45,9 @@
background-color: #EDD5DD
}
.btn_move { width:32px; min-width: 32px; height: 32px; background-color: #fff; border:1px solid #999; border-radius:4px; margin-bottom: 4px; }
</style>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" src="<c:url value="/js/jsplumb.js"/>"></script>
@@ -888,7 +891,7 @@
}
});
$("input[name=auto]").click(function(){
$("#auto").click(function(){
//debugger;
jsPlumbInstance.deleteEveryConnection();
@@ -915,7 +918,7 @@
}
});
$("input[name=addAll]").click(function() {
$("#addAll").click(function() {
console.debug('addAll');
var srcRowId = sourceGrid.getGridParam( "selrow" );
var tgtRowId = targetGrid.getGridParam( "selrow" );
@@ -966,7 +969,7 @@
}
});
$("input[name=removeAll]").click(function(){
$("#removeAll").click(function(){
var tgtRowId = targetGrid.getGridParam( "selrow" );
if (tgtRowId == null) {alert("타겟레이아웃을 선택하여 주십시요.");return;}
//grid data
@@ -986,6 +989,49 @@
}
});
$("#add").click(function() {
var srcRowId = sourceGrid.getGridParam( "selrow" );
var tgtRowId = targetGrid.getGridParam( "selrow" );
if (srcRowId == null) {alert('<%=localeMessage.getString("transformManDetail.alert1")%>');return;}
if (tgtRowId == null) {alert('<%=localeMessage.getString("transformManDetail.alert2")%>');return;}
//field, ATTR 여부 판단
var sourceRow = sourceGrid.getRowData( srcRowId );
var targetRow = targetGrid.getRowData( tgtRowId );
if ( (sourceRow["loutItemType"] == 'Field' || sourceRow["loutItemType"] == 'Attr' || sourceRow["loutItemType"] == 'Array') &&
(targetRow["loutItemType"] == 'Field' || targetRow["loutItemType"] == 'Attr' || targetRow["loutItemType"] == 'Array') ){
;
}else{
alert('<%=localeMessage.getString("transformManDetail.alert3")%>');
return ;
}
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNCMDNAME',addArrayTag(sourceRow['loutItemType'], addGroupTag(srcGroup,sourceRow['LOUTITEMPATH'])));
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNRSULTITEMPATHNAME',addArrayTag(targetRow['loutItemType'], addGroupTag(tgtGroup,targetRow['LOUTITEMPATH'])));
targetGrid.jqGrid('setCell',tgtRowId,'CNVSNITEMSERNO',targetRow['loutItemSerno']);
try {
jsPlumbConnect(srcRowId, tgtRowId);
} catch (e) {console.error(e);}
});
$("#remove").click(function(){
var tgtRowId = targetGrid.getGridParam( "selrow" );
if (tgtRowId == null) {alert("타겟레이아웃을 선택하여 주십시요.");return;}
//grid data
var targetData = targetGrid.getRowData();
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNCMDNAME', null);
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNRSULTITEMPATHNAME', null);
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNITEMSERNO', null);
targetGrid.jqGrid('setCell', tgtRowId, 'CNVSNITEMBASCVAL', null);
const targetEndpointElementId = targetGrid.getEndpointElementId(tgtRowId);
jsPlumbInstance.deleteConnectionsForElement(document.getElementById(targetEndpointElementId));
});
$("textarea[name=CNVSNRSULTITEMPATHNAME],textarea[name=CNVSNCMDNAME],input[name=CNVSNITEMBASCVAL],input[name=CNVSNITEMSERNO]").blur(function(){
var rowId = targetGrid.getGridParam( "selrow" );
if (isNull(rowId)) return;
@@ -2161,12 +2207,12 @@
<div id="sourcePage"></div>
</div>
</td>
<td style="width:30%; text-align:center; vertical-align: top;">
<br /><!-- 이 br 이 없을 때 버튼이 제대로 동작 하지 않는다. -->
<input type=button id="removeAll" name="removeAll" class="btn_img btn_removeAll"/><br />
<input type=button id="addAll" name="addAll" class="btn_img btn_addAll"/><br />
<input type=button id="auto" name="auto" class="btn_img btn_auto" />
<td style="width:30%; min-width:70px; text-align:center; vertical-align: top; ">
<button type=button id="remove" name="remove" class="cssbtn btn_move" title="remove"><</button>
<button type=button id="add" name="add" class="cssbtn btn_move" title="add"> > </button><br />
<button type=button id="removeAll" name="removeAll" class="cssbtn btn_move" title="remove all"> << </button>
<button type=button id="addAll" name="addAll" class="cssbtn btn_move" title="add all"> >> </button><br />
<button type=button id="auto" name="auto" class="cssbtn btn_move" style="width: 70px; " title="auto"> Auto </button>
</td>
<td style="vertical-align:top;">
<table class="table_row" cellspacing="0">
+111 -40
View File
@@ -4,11 +4,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%@ page import="com.eactive.eai.common.util.SystemUtil"%>
<%@ page import="com.eactive.eai.rms.common.context.MonitoringContext"%>
<%@ page import="com.eactive.eai.rms.common.util.CommonUtil"%>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
MonitoringContext monitoringContext = (MonitoringContext)CommonUtil.getBean(request, "monitoringContext");
String deployYnStr = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DEPLOY_YN_BY_RMS, "N");
boolean deployYn = "Y".equalsIgnoreCase(deployYnStr);
String adminOnlyYnStr = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DEPLOY_YN_BY_ADMIN_ONLY, "Y");
boolean adminOnly = "Y".equalsIgnoreCase(adminOnlyYnStr);
%>
<html>
<head>
@@ -17,62 +24,48 @@
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" >
<script>
var url = '<c:url value="/onl/admin/rule/transformMan.json" />';
var url_view = '<c:url value="/onl/admin/rule/transformMan.view" />';
var url_excel= '<c:url value="/onl/admin/rule/transformMan.excel"/>';
var roleString = "<%=SessionManager.getRoleIdString(request)%>";
var devSvr = <%=SystemUtil.isDevServer()%>;
function dateFormater(cellvalue, options, rowObject){
if (cellvalue==null|| cellvalue==undefined) return "";
if (cellvalue.trim().length < 14) return "";
var display = "";
display=display+cellvalue.substr(0,4)+"-";
display=display+cellvalue.substr(4,2)+"-";
display=display+cellvalue.substr(6,2)+" ";
display=display+cellvalue.substr(8,2)+":";
display=display+cellvalue.substr(10,2)+":";
display=display+cellvalue.substr(12,2);
return display;
}
var stageSvr = <%=SystemUtil.isStagingServer()%>;
var adminOnly = <%=adminOnly%>;
$(document).ready(function() {
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
$('#grid').jqGrid({
datatype:"json",
mtype: 'POST',
url: url,
postData : { cmd : 'LIST'
, searchBizCode : $('input[name=searchBizCode]').val()
, searchCnvsnName : $('input[name=searchCnvsnName]').val()
, searchCnvsnDesc : $('input[name=searchCnvsnDesc]').val()
},
postData : postData,
colNames:[
'<%=localeMessage.getString("transformMan.eaibzwkdstcd")%>',
'<%=localeMessage.getString("transformMan.telCngMappCd")%>',
'<%=localeMessage.getString("transformMan.telCngMappDesc")%>',
'<%=localeMessage.getString("transformMan.ifSvcCd")%>',
'<%=localeMessage.getString("transformMan.ifSvcDesc")%>',
'<%=localeMessage.getString("transformMan.eaiSvrDiv")%>',
'<%= localeMessage.getString("combo.useYn")%> ',
'<%=localeMessage.getString("transformMan.modifyDate")%>',
'<%=localeMessage.getString("transformMan.verInfo")%>',
'타입',
'cnvsnType',
'flag'
],
colModel:[
{ name : 'EAIBZWKDSTCD' , align:'center' ,sortable:false , width : "40"},
{ name : 'CNVSNNAME' , align:'left' },
{ name : 'EAISVCNAME' , align:'left' },
{ name : 'EAISVCDESC' , align:'left' },
{ name : 'EAIBZWKDSTCD' , align:'center' ,sortable:false , width:"30"},
{ name : 'CNVSNNAME' , align:'left' , width:"100" },
{ name : 'CNVSNDESC' , align:'left' , width:"100" },
{ name : 'EAISVCNAME' , align:'left' , width:"70" },
{ name : 'EAISVCDESC' , align:'left' , width:"100"},
{ name : 'EAISEVRDSTCD' , hidden : true },
{ name : 'USEYN', align:'center', width:30,editoptions:{value:"0:<%= localeMessage.getString("combo.usen")%>;1:<%= localeMessage.getString("combo.usey")%>"}, formatter:"select"},
{ name : 'LASTAMNDHMS' , align:'center' , formatter: dateFormater, width : "70" },
{ name : 'VERINFO' , align:'center' , width : "70" },
{ name : 'CNVSNTYPE' , hidden : true },
{ name : 'USEYN', align:'center', width:30 ,editoptions:{value:"0:<%= localeMessage.getString("combo.usen")%>;1:<%= localeMessage.getString("combo.usey")%>"}, formatter:"select"},
{ name : 'LASTAMNDHMS' , align:'center' , width : "60" },
{ name : 'VERINFO' , align:'center' , width : "30" },
{ name : 'CNVSNTYPE' , hidden : true },
{ name : 'FLAG' , hidden : true }
],
jsonReader: {
@@ -136,7 +129,8 @@ $(document).ready(function() {
//key값
url2 += '&cnvsnName='+cnvsnName;
url2 += '&eaiSvcName='+eaiSvcName;
url2 += '&eaiSvcDesc='+eaiSvcDesc;
//url2 += '&eaiSvcDesc='+eaiSvcDesc;
url2 += '&eaiSvcDesc='+encodeURIComponent(eaiSvcDesc);
url2 += '&cnvsnDesc='+cnvsnDesc;
url2 += '&eaiSevrDStcd='+eaiSevrDStcd;
url2 += '&useYn='+useYn;
@@ -161,7 +155,6 @@ $(document).ready(function() {
}
});
resizeJqGridWidth('grid','content_middle','1000');
$("#btn_search").click(function(){
@@ -217,7 +210,7 @@ $(document).ready(function() {
}
return false;
}else{
if(confirm("초기화 하시겠습니까?")){
if(confirm("<%=localeMessage.getString("eaiMessage.confirm2")%>")){
var postData = [];
postData.push({ name: "cmd" , value:"LIST_INITIALIZE"});
postData.push({ name: "chkItems" , value : chkArr});
@@ -242,8 +235,13 @@ $(document).ready(function() {
});
$('#btn_cmsave').click(onclickBtnCmSave);
$('#btn_clone').click(onclickBtnClone);
$("#btn_delete").click(function(){
if(!confirm("삭제하시겠습니까?")){
return;
}
var id = "grid";
var cnvsnNameArr = new Array();
var eaiSvcNameArr = new Array();
@@ -263,7 +261,7 @@ $(document).ready(function() {
}
if(chkcnt == 0){
alert("변환매핑을 선택해 주세요");
alert("<%= localeMessage.getString("standardLayout.message4") %>");
return false;
}
@@ -303,10 +301,18 @@ $(document).ready(function() {
});
buttonControl();
setBtnHide(roleString, "admin", "btn_initialize");
//setBtnControl(roleString, "admin", "btn_cmsave");
if(!devSvr){
$("#btn_cmsave").hide();
if(devSvr || stageSvr){
if (adminOnly) {
setBtnHide(roleString, "admin", "btn_clone");
}
}else{
$("#btn_clone").hide();
}
//setBtnControl(roleString, "admin", "btn_cmsave");
//if(!devSvr){
// $("#btn_cmsave").hide();
//}
});
function setBtnControl(roleString, condiVal, compoId)
@@ -430,6 +436,62 @@ function addCMResource(eaiBzwkDsdCd, obj, cnvsnNames) {
});
}
function onclickBtnClone() {
var sel = $('#grid').jqGrid('getGridParam', 'selarrrow');
if (sel.length == 0) {
alert('<%= localeMessage.getString("eaiMessage.alert17")%>');
return;
}
var selectedTransformNames = [];
$(sel).each(function(index, i) {
var cnvsnName = $('#grid').getCell(i, 'CNVSNNAME');
selectedTransformNames.push(cnvsnName);
});
var url2 = url_view;
url2 += "?cmd=POPUP CALENDAR";
var args = new Object();
args['selectedCnvsnName'] = selectedTransformNames.join(',');
showModal(url2,args,540,270, function(arg){
var args = null;
if(arg == null || arg == undefined ) {
//chrome
args = this.dialogArguments;
args.returnValue = this.returnValue;
} else {
//ie
args = arg;
}
if( !args || !args.returnValue ) return;
deployTransforms(args.returnValue);
});
}
function deployTransforms(ret) {
var params = ret;
params['cmd'] = 'CLONETOPROD';
$.ajax({
type : "POST",
url:url,
dataType:"json",
data:params,
success:function(json){
alert(json.message);
list($("#grid").getGridParam("page"));
},
error:function(xhr,status,error){
if (xhr.responseText != null) {
try {
alert(JSON.parse(xhr.responseText).errorMsg);
} catch (e) {
alert(error);
}
}
}
});
}
</script>
</head>
<body>
@@ -441,6 +503,11 @@ function addCMResource(eaiBzwkDsdCd, obj, cnvsnNames) {
</div><!-- end content_top -->
<div class="content_middle" id="content_middle">
<div class="search_wrap">
<% if (SystemUtil.isDevServer() && deployYn) { %>
<button type="button" class="cssbtn" id="btn_clone" level="W"><i class="material-icons">move_up</i> <%= localeMessage.getString("button.copyStage") %></button>
<% } else if (SystemUtil.isStagingServer() && deployYn) { %>
<button type="button" class="cssbtn" id="btn_clone" level="W"><i class="material-icons">move_up</i> <%= localeMessage.getString("button.copyProd") %></button>
<% } %>
<button type="button" class="cssbtn" id="btn_excel" level="W" status="DETAIL"><i class="material-icons">table_view</i> <%= localeMessage.getString("button.excel") %></button>
<button type="button" class="cssbtn" id="btn_initialize" level="W" ><i class="material-icons">restart_alt</i> <%= localeMessage.getString("button.reset") %></button>
<button type="button" class="cssbtn" id="btn_delete" level="W" status="DETAIL,NEW"><i class="material-icons">delete</i> <%= localeMessage.getString("button.delete") %></button>
@@ -469,9 +536,13 @@ function addCMResource(eaiBzwkDsdCd, obj, cnvsnNames) {
</tr>
<tr>
<th style="width:180px;"><%=localeMessage.getString("transformMan.telCngMappDesc")%> </th>
<td colspan="3">
<td>
<input type="text" name="searchCnvsnDesc" value="${param.searchCnvsnDesc}">
</td>
<th style="width:180px;"><%=localeMessage.getString("transformMan.ifSvcCd")%> </th>
<td>
<input type="text" name="searchIfSvcCd" value="${param.searchIfSvcCd}">
</td>
</tr>
</tbody>
</table>
@@ -0,0 +1,258 @@
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.io.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
%>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr">
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" >
var url = '<c:url value="/onl/transaction/apim/apiInterfaceMan.json"/>';
var url_view = '<c:url value="/onl/transaction/apim/apiInterfaceMan.view"/>';
var selectName = "searchEaiBzwkDstcd"; // selectBox Name
function processSelectedData() {
var grid = $("#grid");
var selectedRowIds = grid.jqGrid('getGridParam', 'selarrrow');
var selectedData = [];
for(var i = 0; i < selectedRowIds.length; i++) {
var rowData = grid.jqGrid('getRowData', selectedRowIds[i]);
selectedData.push({
bizCode: rowData.eaiBzwkDstcd, // CLINET
apiId: rowData.eaiSvcName,
apiDesc: rowData.eaiSvcDesc, // SCOPE-API
APIFULLPATH: rowData.apiFullPath,
BZWKSVCKEYNAME: rowData.bzwksvckeyname
});
}
if(selectedData.length === 0) {
alert("선택된 항목이 없습니다.");
return;
}
alert("선택한 " + selectedData.length + "건의 API를 추가합니다.");
window.returnValue = selectedData.length === 1 ? selectedData[0] : selectedData;
window.close();
}
function updateSelectionInfo(gridId) {
var grid = $(gridId || "#grid");
var selectedRows = grid.jqGrid('getGridParam', 'selarrrow');
$("#selected-count").text("총: " + selectedRows.length + " 건");
}
function init( callback) {
$.ajax({
type : "POST",
url:url,
dataType:"json",
data:{cmd: 'LIST_COMBO'},
success:function(json){
new makeOptions("BIZCODE","BIZNAME").setObj($("select[name=searchEaiBzwkDstcd]")).setNoValueInclude(true).setNoValue("","전체").setData(json.bizList).setFormat(codeName3OptionFormat).rendering();
setSearchable(selectName); // 콤보에 searchable 설정
if (typeof callback === 'function') {
callback();
}
},
error:function(e){
alert(e.responseText);
}
});
}
function detail(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
$("#grid").setGridParam({ url:url,postData: postData }).trigger("reloadGrid");
}
function search(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
}
function ioFormatter(cellvalue,options,rowObject){
var serviceType = sessionStorage["serviceType"];
if(cellvalue == "I"){
return "타발";
}else if(cellvalue =="O"){
return "당발";
}
}
function adapterNameShortFormatter(cellvalue,options,rowObject){
if(cellvalue == null || cellvalue == '')
return ''
return cellvalue.substring(1, 4);
}
function list(){
detail()
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
// gridPostData.searchApiEnableYn = 'Y';
var urlParams = new URLSearchParams(window.location.search);
var selectedApiIds = urlParams.get("selectedApiIds");
$('#grid').jqGrid({
datatype : "json",
mtype : 'POST',
url : url,
postData : gridPostData,
colNames : [ '업무구분',
'<%= localeMessage.getString("eaiMessage.eaiSvcName")%>',
'<%= localeMessage.getString("eaiMessage.eaiSvcDesc")%>',
'API FULL PATH',
'업무서비스명',
'요청',
'응답',
'작성자',
'가상응답여부',
'SyncAsyncType',
],
colModel : [ { name : 'eaiBzwkDstcd' , align : 'center' , width:'40', sortable:false},
{ name : 'eaiSvcName' , align : 'left' , width:'100'},
{ name : 'eaiSvcDesc' , align : 'left' },
{ name : 'apiFullPath' , align : 'left' , width:'100'},
{ name : 'bzwksvckeyname' , align : 'left' , width:'100', hidden: true},
{ name : 'fromAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter },
{ name : 'toAdapter' , align : 'center' , width:'40', formatter:adapterNameShortFormatter },
{ name : 'author' , align : 'center' , width:'60' },
{ name : 'simYn' , align : 'center' , width:'40' },
{ name : 'syncAsyncType' , align : 'center' , width:'40', hidden: true},
],
jsonReader : {
repeatitems : false
},
pager : $('#pager'),
page : '${param.page}',
rowNum : '${rmsDefaultRowNum}',
autoheight : true,
height : $("#container").height(),
autowidth : true,
viewrecords : true,
multiselect: false,
multiboxonly: false,
rowList : eval('[${rmsDefaultRowList}]'),
loadComplete:function (d){
var $grid = $(this);
if(selectedApiIds) {
const selectedIds = selectedApiIds.split(',');
$.each($grid.getDataIDs(), function(_, id) {
var rowData = $grid.getRowData(id);
if(selectedIds.includes(rowData.eaiSvcName)) {
$grid.jqGrid('setSelection', id, true);
}
});
}
var colModel = $(this).getGridParam("colModel");
for(var i = 0 ; i< colModel.length; i++){
$(this).setColProp(colModel[i].name, {sortable : false});
}
updateSelectionInfo("#grid");
},
onSelectRow: function(rowid, status, e) {
updateSelectionInfo("#grid");
},
onSelectAll: function(rowids, status) {
updateSelectionInfo("#grid");
},
ondblClickRow : function(rowId) {
const rowData = $(this).getRowData(rowId);
const returnValue = {
bizCode: rowData.eaiBzwkDstcd,
apiId: rowData.eaiSvcName,
apiDesc: rowData.eaiSvcDesc
};
//alert("선택한 API를 추가합니다.\nAPI ID: " + rowData.eaiSvcName);
window.returnValue = returnValue;
window.close();
}
});
}
$(document).ready(function() {
init(list);
resizeJqGridWidth('grid','content_middle','1000');
// 업무구분명 선택 시 자동 검색
$("select[name=searchEaiBzwkDstcd]").change(function() {
search();
});
$("#btn_search").click(function(){
search();
});
$("input[name^=search]").keydown(function(key){
if (key.keyCode == 13){
$("#btn_search").click();
}
});
buttonControl();
$("#btn_select").click(function(){
processSelectedData();
});
$("#btn_close").click(function () {
window.close();
});
});
</script>
</head>
<body>
<div>
<div class="content_middle" id="content_middle" style="margin-top: 0px">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_search" level="R" ><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
<button type="button" class="cssbtn" id="btn_select" level="R" ><i class="material-icons">check</i> <%= localeMessage.getString("button.check") %></button>
<button type="button" class="cssbtn" id="btn_close" level="R" status="DETAIL"><i class="material-icons">close</i> <%= localeMessage.getString("button.close") %></button>
</div>
<div class="title" id="title" >${rmsMenuName}</div>
<!-- 선택 정보 표시 영역 추가 -->
<div style="margin-bottom: 5px;">
<span id="selected-count" style="color: #666; font-size: 12px;"></span>
</div>
<table class="search_condition" cellspacing=0;>
<tbody>
<tr>
<th style="width:180px;">업무구분명</th>
<td>
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}">
</select>
</td>
<th style="width:180px;"><%= localeMessage.getString("eaiMessage.eaiSvcName")%></th>
<td>
<input type="text" name="searchEaiSvcName" value="${param.searchEaiSvcName}">
</td>
</tr>
<tr>
<th style="width:180px;"><%= localeMessage.getString("eaiMessage.eaiSvcDesc")%></th>
<td>
<input type="text" name="searchEaiSvcDesc" value="${param.searchEaiSvcDesc}">
</td>
<th style="width:180px;">API FULL PATH</th>
<td>
<input type="text" name="searchApiFullPath" value="${param.searchApiFullPath}">
</td>
</tr>
</tbody>
</table>
<table id="grid" ></table>
<div id="pager"></div>
</div><!-- end content_middle -->
</div><!-- end right_box -->
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -46,10 +46,10 @@ $(document).ready(function() {
, src_layout : src_layout
},
colNames:[
'<%=localeMessage.getString("tranManDtPop.conRsItPtNm")%>',
'<%=localeMessage.getString("tranManDtPop.conOrd")%>',
'<%=localeMessage.getString("tranManDtPop.ItDft")%>',
'<%=localeMessage.getString("tranManDtPop.itSrlNmb")%>',
'변환결과 항목경로명',
'변환명령',
'항목 기본값',
'항목 일련번호',
'타겟Flag',
'소스Flag',
'<%=localeMessage.getString("tranManDtPop.errMsg")%>'
@@ -104,7 +104,6 @@ $(document).ready(function() {
resizeJqGridWidth('grid','title','1000');
$("#btn_close").click(function(){
//debugger;
window.close();
});
$("body").css("overflow-x","hidden");
@@ -115,13 +114,12 @@ $(document).ready(function() {
<body>
<div class="popup_box" id="title">
<div class="search_wrap">
<%-- <img id="btn_close" src="<c:url value="/img/btn_close.png"/>" level="R"/> --%>
<button type="button" class="cssbtn" id="btn_close" level="R"><i class="material-icons">cancel</i> <%= localeMessage.getString("button.close") %></button>
<img id="btn_close" src="<c:url value="/img/btn_close.png"/>" level="R"/>
</div>
<div class="title"><%=localeMessage.getString("tranManDtPop.title")%></div>
<div class="title">변환매핑항목 상세</div>
<table class="search_condition" cellspacing="0">
<tr>
<th style="width:180px;"><%=localeMessage.getString("tranManDtPop.conMapCd")%></th>
<th style="width:180px;">변환매핑 코드</th>
<td>
<input type="text" name="cnvsnName" readonly>
</td>
@@ -20,6 +20,7 @@
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript" >
var url_view = '<c:url value="/onl/admin/rule/transformMan.view" />';
var $ = jQuery.noConflict();
var bzwkDstCd = "";
@@ -81,8 +82,9 @@ $(document).ready(function() {
var key = "";
var args = new Object();
args['eaiSvcName'] = $('input[name=eaiSvcName]').val();
var url='<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
url = url + "?cmd=POPUP";
var url=url_view; //'<c:url value="/onl/transaction/extnl/interfaceMan.view"/>';
url = url + "?cmd=API_POPUP";
console.log('[new]', url);
var ret = showModal(url,args,1020,630, function(arg){
var args = null;
if(arg == null || arg == undefined ) {//chrome
@@ -96,15 +98,15 @@ $(document).ready(function() {
var ret = args.returnValue;
console.log("ret",ret);
key = ret['key'];
key = ret['apiId'];
$("input[name=eaiSvcName]").val(key);
$("input[name=eaiSvcDesc]").val(ret['eaiSvcDesc']);
bzwkDstCd = ret['eaiBzwkDstCd'];
$("input[name=eaiSvcDesc]").val(ret['apiDesc']);
bzwkDstCd = ret['bizCode'];
$("input[name=eaiSvcName]").change();
$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']);
$("select[name=eaiSevrDstcd]").attr("disabled",true);
//$("select[name=eaiSevrDstcd]").val(ret['eaiSevrDstcd']);
//$("select[name=eaiSevrDstcd]").attr("disabled",true);
});
});
@@ -159,25 +161,29 @@ $(document).ready(function() {
<form id="ajaxForm">
<table class="table_row" cellspacing="0">
<tr>
<th style="width:25%;"><%=localeMessage.getString("transformMan.ifSvcCd")%> </th>
<td colspan="3">
<input type="text" name="eaiSvcName" style="width:calc(100% - 60px);" readonly> <%-- <img id="btn_search" src="<c:url value="/img/btn_pop_search.png" />" level="R" class="btn_img" /> --%>
<th style="width:14%; min-width:130px;"><%=localeMessage.getString("transformMan.ifSvcCd")%> </th>
<td style="width:31%;">
<input type="text" name="eaiSvcName" style="width:60%;" readonly> <!-- <img id="btn_search" src="<c:url value="/img/btn_pop_search.png" />" level="R" class="btn_img" /> -->
<button type="button" class="cssbtn smallBtn2" id="btn_search" style="vertical-align:middle; font-weight:bold;" level="R"><i class="material-icons">search</i><%= localeMessage.getString("button.search") %></button>
</td>
</tr>
<tr>
<th><%=localeMessage.getString("transformMan.ifSvcDesc")%></th>
<td colspan="3">
<th style="width:14%; min-width:130px;"><%=localeMessage.getString("transformMan.ifSvcDesc")%></th>
<td style="width:41%;">
<input type="text" name="eaiSvcDesc" readOnly>
</td>
</tr>
<tr>
<th><%=localeMessage.getString("transformMan.eaiSvrDiv")%></th>
<td colspan="3">
<td>
<div class="select-style">
<select name="eaiSevrDstcd" />
</div>
</td>
<th style="width:13%; min-width:130px;"><%=localeMessage.getString("combo.useYn")%></th>
<td>
<div class="select-style">
<select name="useYn" />
</div>
</td>
</tr>
<tr>
<th><%=localeMessage.getString("transformMan.cnvsnType")%></th>
@@ -186,20 +192,13 @@ $(document).ready(function() {
<select name="cnvsnType" />
</div>
</td>
<th><%=localeMessage.getString("combo.useYn")%></th>
<td>
<div class="select-style">
<select name="useYn" />
</div>
</td>
</tr>
<tr>
<th><%=localeMessage.getString("transformMan.telCngMappCd")%></th>
<td colspan="3"><input type="text" name="cnvsnName" readonly="readonly"/></td>
<td><input type="text" name="cnvsnName" readonly="readonly"/></td>
</tr>
<tr height="100px">
<th><%=localeMessage.getString("transformMan.cnvsnDesc")%></th><td colspan="3"><textarea name="cnvsnDesc" style="width:100%;height:100px"></textarea></td>
<tr>
<th><%=localeMessage.getString("transformMan.cnvsnDesc")%></th><td colspan="3"><textarea name="cnvsnDesc" style="width:100%;height:50px"></textarea></td>
</tr>
</table>
</form>
+12 -24
View File
@@ -47,9 +47,6 @@ function init(callback){
dataType:"json",
data:{cmd: 'LIST_INIT', codeGroupArr : 'SVC_SAME_TIME_TYPE,<%= tranTypeCode %>'},
success:function(json){
new makeOptions("CODE","NAME").setObj($("select[name=searchSvcMotivUsedStcd]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.list[0].SVC_SAME_TIME_TYPE).rendering();
new makeOptions("CODE","NAME").setObj($("select[name=searchPsvintfacdsticName]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.list[0].SVC_SAME_TIME_TYPE).rendering();
new makeOptions("CODE","NAME").setObj($("select[name=searchFlowControl]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.listFlowControl).rendering();
new makeOptions("CODE","NAME").setObj($("select[name=searchTranType]")).setNoValueInclude(true).setNoValue("", "<%=localeMessage.getString("combo.all")%>").setData(json.list[0].<%= tranTypeCode %>).rendering();
svrInst = json.listInoutType;
putSelectFromParam();
@@ -89,8 +86,6 @@ $(document).ready(function() {
gridPostData["rmsMenuAuth"]= auth;
init(detail);
gridPostData["searchSvcMotivUsedStcd"] = '${param.searchSvcMotivUsedStcd}';
gridPostData["searchPsvintfacdsticName"] = '${param.searchPsvintfacdsticName}';
$('#grid').jqGrid({
datatype:"json",
@@ -100,10 +95,8 @@ $(document).ready(function() {
colNames:['<%= localeMessage.getString("eaiMsg.bizCode")%> ',
'<%= localeMessage.getString("eaiMessage.eaiSvcName")%> ',
'<%= localeMessage.getString("eaiMessage.eaiSvcDesc")%> ',
'<%= localeMessage.getString("eaiMessage.svcMotivUsedStcd")%>',
'<%= localeMessage.getString("eaiMessage.psvintfacdsticName")%>',
'<%= localeMessage.getString("eaiMessage.queueName")%> ',
'<%= localeMessage.getString("eaiMessage.flowCtrlRoutName")%> ',
'<%= localeMessage.getString("eaiMessage.gstatSysAdptr")%>',
'<%= localeMessage.getString("eaiMessage.psvSysAdptr")%>',
'<%= localeMessage.getString("eaiMessage.tranType")%> ',
'<%= localeMessage.getString("combo.useYn")%> ',
'<%= localeMessage.getString("eaiMessage.sevrDstCd")%>',
@@ -113,10 +106,8 @@ $(document).ready(function() {
{ name : 'EAIBZWKDSTCD' , align:'center' ,sortable:false, width:20 },
{ name : 'EAISVCNAME' , align:'left', width:50 },
{ name : 'EAISVCDESC' , align:'left' },
{ name : 'SVCMOTIVUSEDSTCD', align:'center', width:50 },
{ name : 'PSVINTFACDSTICNAME', align:'center', width:50 },
{ name : 'NONMOTIVROUTURINAME' , align:'left', width:90 },
{ name : 'FLOWCTRLROUTNAME' , align:'center', width:40 },
{ name : 'GSTATSYSADPTRBZWKGROUPNAME', align:'left', width:80 },
{ name : 'PSVSYSADPTRBZWKGROUPNAME', align:'left', width:80 },
{ name : 'TRANTYPE', align:'center', width:30 },
{ name : 'USEYN', align:'center', width:30,editoptions:{value:"0:<%= localeMessage.getString("combo.usen")%>;1:<%= localeMessage.getString("combo.usey")%>"}, formatter:"select"},
{ name : 'EAISEVRDSTCD', align:'center', width:30 , formatter:formatter, hidden:true},
@@ -426,28 +417,25 @@ function deployInterfaces(ret) {
<td>
<input type="text" name="searchEaiSvcName" value="${param.searchEaiSvcName}">
</td>
<th style="width:10%; min-width:120px;"><%= localeMessage.getString("eaiMessage.svcMotivUsedStcd")%></th>
<td><div class="select-style"><select name="searchSvcMotivUsedStcd" value="${param.searchSvcMotivUsedStcd}"></select></div></td>
<th style="width:10%; min-width:90px;"><%= localeMessage.getString("eaiMessage.tranType")%></th>
<td><div class="select-style"><select name="searchTranType" value="${param.searchTranType}"></select></div></td>
<th style="width:10%; min-width:90px;"><%= localeMessage.getString("eaiMessage.eaiSvcDesc")%></th>
<td>
<input type="text" name="searchEaiSvcDesc" value="${param.searchEaiSvcDesc}">
</td>
<th style="width:10%; min-width:90px;"><%= localeMessage.getString("eaiMessage.tranType")%></th>
<td><div class="select-style"><select name="searchTranType" value="${param.searchTranType}"></select></div></td>
</tr>
<tr>
<th><%= localeMessage.getString("eaiMessage.searchBzwkDstCd")%></th>
<td>
<input type="text" name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}">
</td>
<th><%= localeMessage.getString("eaiMessage.psvintfacdsticName")%></th>
<td><div class="select-style"><select name="searchPsvintfacdsticName" value="${param.searchPsvintfacdsticName}"></select></div></td>
<th><%= localeMessage.getString("eaiMessage.searchFlowControl")%></th>
<td><div class="select-style"><select name="searchFlowControl" value="${param.searchFlowControl}"></select></div></td>
<th><%= localeMessage.getString("eaiMessage.queueName")%></th>
<th><%= localeMessage.getString("eaiMessage.gstatSysAdptr")%></th>
<td>
<input type="text" name="searchQueueName" value="${param.searchQueueName}">
<input type="text" name="searchGstatSysAdptrBzwkGroupName" value="${param.searchGstatSysAdptrBzwkGroupName}">
</td>
<th><%= localeMessage.getString("eaiMessage.psvSysAdptr")%></th>
<td>
<input type="text" name="searchPsvSysAdptrBzwkGroupName" value="${param.searchPsvSysAdptrBzwkGroupName}">
</td>
</tr>
</tbody>
@@ -72,13 +72,22 @@
});
}
function detail(){
// 체크박스는 name 을 search* 로 두면 getSearchForJqgrid 가 해제 상태에서도 value 를 담아버린다
// (input[type!=radio][name^=search] 를 checked 여부와 무관하게 수집) → 여기서 직접 붙인다
function getPopupSearchData() {
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
postData.searchApiSpecYn = $("#chkApiSpec").is(":checked") ? "Y" : "";
postData.searchPortalDisplayYn = $("#chkPortalDisplay").is(":checked") ? "Y" : "";
return postData;
}
function detail(){
var postData = getPopupSearchData();
$("#grid").setGridParam({ url:url,postData: postData }).trigger("reloadGrid");
}
function search(){
var postData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
var postData = getPopupSearchData();
$("#grid").setGridParam({ url:url,postData: postData ,page:1 }).trigger("reloadGrid");
}
function ioFormatter(cellvalue,options,rowObject){
@@ -97,7 +106,7 @@
function list(){
detail()
var gridPostData = getSearchForJqgrid("cmd","LIST"); //jqgrid에서는 object 로
var gridPostData = getPopupSearchData();
// gridPostData.searchApiEnableYn = 'Y';
var urlParams = new URLSearchParams(window.location.search);
var selectedApiIds = urlParams.get("selectedApiIds");
@@ -189,6 +198,9 @@
$("select[name=searchEaiBzwkDstcd]").change(function() {
search();
});
$("#chkApiSpec, #chkPortalDisplay").change(function() {
search();
});
$("#btn_search").click(function(){
search();
});
@@ -247,6 +259,18 @@
<input type="text" name="searchApiFullPath" value="${param.searchApiFullPath}">
</td>
</tr>
<tr>
<th style="width:180px;">포탈 노출</th>
<td colspan="3">
<label class="checkbox-inline">
<input type="checkbox" id="chkApiSpec" <c:if test="${param.searchApiSpecYn eq 'Y'}">checked</c:if>> OpenAPI Spec 등록
</label>
&nbsp;&nbsp;
<label class="checkbox-inline">
<input type="checkbox" id="chkPortalDisplay" <c:if test="${param.searchPortalDisplayYn eq 'Y'}">checked</c:if>> 포탈 게시
</label>
</td>
</tr>
</tbody>
</table>
<table id="grid" ></table>
@@ -183,6 +183,17 @@
var currentRequestType = null; // 현재 신청 유형 (DELETE 승인 시 GW 처리방식 선택 노출용)
// 값이 있을 때만 행을 노출한다. (해지 신청처럼 해당 항목이 없는 유형에서 빈 행이 남지 않도록)
function setOptionalRow(rowId, spanId, value) {
if (value) {
$("#" + spanId).text(value);
$("#" + rowId).show();
} else {
$("#" + spanId).text("");
$("#" + rowId).hide();
}
}
function detail(key) {
$.ajax({
type: "POST",
@@ -264,6 +275,21 @@
$("#clientName").text(data.clientName);
// 신청서에 담긴 Client 등록 정보(승인 시 GW 클라이언트로 반영되는 값). 값이 있을 때만 노출
setOptionalRow("appDescriptionRow", "appDescription", data.appDescription);
setOptionalRow("ipWhitelistRow", "ipWhitelist", data.ipWhitelist);
setOptionalRow("callbackUrlRow", "callbackUrl", data.callbackUrl);
// 신청 사유. 해지 신청은 사유 입력이 필수이므로 "해지 사유"로 표기하고, 그 외 유형은 "신청 사유"
if (data.requestReason) {
$("#requestReasonLabel").text(data.requestType === 'DELETE' ? "해지 사유" : "신청 사유");
$("#requestReason").text(data.requestReason);
$("#requestReasonRow").show();
} else {
$("#requestReason").text("");
$("#requestReasonRow").hide();
}
var apiServiceHtml = "";
for (var i = 0; i < data.apiServices.length; i++) {
var apiService = data.apiServices[i];
@@ -365,6 +391,22 @@
<th style="width:20%;">이름</th>
<td><span id="clientName"></span></td>
</tr>
<tr id="appDescriptionRow" style="display:none;">
<th style="width:20%;">앱 설명</th>
<td><span id="appDescription" style="white-space: pre-wrap;"></span></td>
</tr>
<tr id="ipWhitelistRow" style="display:none;">
<th style="width:20%;">IP 화이트리스트</th>
<td><span id="ipWhitelist" style="white-space: pre-wrap;"></span></td>
</tr>
<tr id="callbackUrlRow" style="display:none;">
<th style="width:20%;">Callback URL</th>
<td><span id="callbackUrl" style="white-space: pre-wrap;"></span></td>
</tr>
<tr id="requestReasonRow" style="display:none;">
<th style="width:20%;"><span id="requestReasonLabel">신청 사유</span></th>
<td><span id="requestReason" style="white-space: pre-wrap;"></span></td>
</tr>
</table>
<div class="title">API 목록</div>
<table id="apisTable" class="table_row" cellspacing="0">
@@ -62,7 +62,19 @@
return '<span title="' + escapeAttr(rowObject.sentDateFull || '') + '">' + escapeHtmlJs(rowObject.sentDateShort || '') + '</span>';
}
function toDashDate(yyyymmdd) {
return yyyymmdd.substring(0, 4) + "-" + yyyymmdd.substring(4, 6) + "-" + yyyymmdd.substring(6, 8);
}
function init() {
$("#startDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
$("#endDatepicker").datepicker().inputmask("yyyy-mm-dd",{'autoUnmask':true});
var today = getToday();
$("input[name=searchFromDate]").val(toDashDate(today.substring(0,6)+"01"));
$("input[name=searchToDate]").val(toDashDate(today));
$.ajax({
type: "POST", url: url, dataType: "json",
data: {cmd: 'LIST_INIT_COMBO'},
@@ -145,6 +157,20 @@
init();
$("#btn_search").click(function () {
var start = $("input[name=searchFromDate]").val().replace(/-/gi, "");
var end = $("input[name=searchToDate]").val().replace(/-/gi, "");
if (start && start > getToday()) {
alert("시작일이 오늘 이후입니다. 시작일을 확인해주세요.");
$("input[name=searchFromDate]").focus();
return false;
}
if (start && end && start > end) {
alert("조회기간 종료일은 시작일보다 커야합니다.");
$("input[name=searchToDate]").focus();
return false;
}
var postData = getSearchForJqgrid("cmd", "LIST");
$("#grid").setGridParam({url: url, postData: postData, page: 1}).trigger("reloadGrid");
});
@@ -198,7 +224,9 @@
</td>
<th>요청기간</th>
<td colspan="3">
<input type="date" name="searchFromDate"> ~ <input type="date" name="searchToDate">
<input type="text" name="searchFromDate" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
~
<input type="text" name="searchToDate" value="" id="endDatepicker" size="10" readonly="readonly" style="width:100px; border:1px solid #ebebec;">
</td>
</tr>
</tbody>
@@ -189,7 +189,7 @@
<td>
<input type="text" id="keywordList" style="width:100%" placeholder="쉼표(,)로 구분하여 입력">
<div class="help-text" style="color:#666; font-size:12px; margin-top:5px;">
예시: #인증, #대출, #예금
예시: 인증, 대출, 예금
</div>
</td>
</tr>
@@ -327,14 +327,61 @@
}, 'json').fail(ajaxFail);
}
// ── 캐시 Reload ──
var reloadModal;
var reloadBtnHtml;
function reloadPortalCache() {
var $btn = $('#btnReloadCache');
if ($btn.prop('disabled')) return;
if (reloadBtnHtml === undefined) reloadBtnHtml = $btn.html();
$btn.prop('disabled', true)
.html('<span class="spinner-border spinner-border-sm"></span> 리로드 중...');
$.post(url, { cmd: 'TRANSACTION_RELOAD' }, function (result) {
if (result && result.success) {
alert('포탈 메뉴 캐시 리로드 완료 (항목 ' + result.itemCount + '건, ' + result.reloadedAt + ')');
} else {
alert('캐시 리로드 실패: ' + (result ? result.message : '알 수 없는 오류'));
}
}, 'json').fail(ajaxFail);
showReloadResult(result);
}, 'json').fail(ajaxFail).always(function () {
$btn.prop('disabled', false).html(reloadBtnHtml);
});
}
function showReloadResult(result) {
if (!result) {
alert('캐시 리로드 실패: 알 수 없는 오류');
return;
}
var servers = result.servers || [];
var $summary = $('#reloadSummary');
$summary.removeClass('alert-success alert-danger alert-warning')
.addClass(result.success ? 'alert-success'
: (result.successCount > 0 ? 'alert-warning' : 'alert-danger'))
.text(result.message || '');
var rows = '';
servers.forEach(function (s) {
var badge = s.success
? '<span class="badge text-bg-success">성공</span>'
: '<span class="badge text-bg-danger">실패</span>';
var detail = s.success
? ('항목 ' + (s.itemCount == null ? '-' : s.itemCount) + '건'
+ (s.reloadedAt ? ' / ' + escapeHtml(String(s.reloadedAt)) : ''))
: escapeHtml(String(s.message || '-'));
rows += '<tr>'
+ '<td class="text-center">' + badge + '</td>'
+ '<td style="word-break:break-all;">' + escapeHtml(String(s.url || '')) + '</td>'
+ '<td>' + detail + '</td>'
+ '<td class="text-end">' + (s.elapsedMs == null ? '-' : s.elapsedMs + 'ms') + '</td>'
+ '</tr>';
});
if (rows === '') {
rows = '<tr><td colspan="4" class="text-center text-muted">'
+ '호출 대상 서버가 없습니다. 아래 설정 안내를 확인하세요.</td></tr>';
}
$('#reloadResultBody').html(rows);
if (!reloadModal) reloadModal = new bootstrap.Modal(document.getElementById('reloadResultModal'));
reloadModal.show();
}
function ajaxFail(xhr) {
@@ -453,7 +500,7 @@
<button type="button" class="btn btn-sm btn-outline-danger" level="W" onclick="initializeMenus()">
<i class="bi bi-arrow-counterclockwise"></i> 메뉴 초기화</button>
<span class="spacer"></span>
<button type="button" class="btn btn-sm btn-outline-secondary" level="W" onclick="reloadPortalCache()">
<button type="button" class="btn btn-sm btn-outline-secondary" id="btnReloadCache" level="W" onclick="reloadPortalCache()">
<i class="bi bi-arrow-repeat"></i> 캐시 Reload</button>
<button type="button" class="btn btn-sm btn-success" level="W" onclick="savePlacements()">
<i class="bi bi-save"></i> 저장</button>
@@ -463,6 +510,12 @@
드래그로 위치를 조절한 뒤 <strong>저장</strong> 버튼으로 일괄 반영합니다.
기본 항목([기본] 배지)은 [-] 시 <strong>미배치</strong>로만 이동하고, 커스텀 항목은 미배치 시 <strong>삭제</strong>됩니다.
저장 후 <strong>캐시 Reload</strong> 를 실행해야 포탈에 즉시 반영됩니다(미실행 시 TTL 경과 후 반영).
<br>
캐시 Reload 대상 서버는 포탈 프로퍼티 <code>Portal / portal.internal.menu-reload-url</code> 로 설정합니다.
포탈이 <strong>다중 서버(이중화)</strong> 로 기동된 경우 모든 서버의 리로드 URL 을
<strong>콤마(,) 또는 줄바꿈</strong> 으로 나열해야 전체 서버에 반영됩니다
(예: <code>http://10.0.0.11:39130/internal/menu/reload, http://10.0.0.12:39130/internal/menu/reload</code>).
서버별 요청 타임아웃은 <strong>3초</strong> 이며, 실행 결과는 서버별로 표시됩니다.
</div>
<div class="menu-board">
@@ -544,6 +597,39 @@
</div>
</div>
<!-- 캐시 Reload 결과 -->
<div class="modal fade" id="reloadResultModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">포탈 메뉴 캐시 Reload 결과</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body">
<div class="alert py-2 px-3" id="reloadSummary" style="font-size:13px;"></div>
<table class="table table-sm table-bordered align-middle" style="font-size:12px;">
<thead class="table-light">
<tr>
<th style="width:70px;" class="text-center">결과</th>
<th>서버 URL</th>
<th style="width:38%;">상세</th>
<th style="width:80px;" class="text-end">소요</th>
</tr>
</thead>
<tbody id="reloadResultBody"></tbody>
</table>
<div class="text-muted" style="font-size:12px;">
대상 서버는 포탈 프로퍼티 <code>Portal / portal.internal.menu-reload-url</code> 에
콤마(,) 또는 줄바꿈으로 나열합니다. 서버별 타임아웃 3초, 전 서버 병렬 호출입니다.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
</div><!-- end content_middle -->
</div><!-- end right_box -->
</body>
@@ -189,8 +189,11 @@
// AuthorizeInterceptor 가 정상적으로 권한 인식. window.open 직접 호출 시 권한 부족 발생.
// 본 페이지는 summernote 본문 에디터 등 다른 iframe 이 함께 존재할 수 있어
// frames[0] 가정은 위험 → DOM 에서 .ui-dialog 내 iframe 중 src=apiGroupMan 인 것을 식별.
// 공지 영향 API 는 포탈에 실제 노출되는 것만 고르면 되므로 두 조건을 켠 채로 연다.
// (타 화면에서 여는 팝업은 파라미터가 없어 해제 상태 — 팝업 기본값은 건드리지 않는다)
var selectedIds = affectedApis.map(function(a) { return a.apiId; }).join(',');
var url2 = apiPopupUrl + '?cmd=POPUP&selectedApiIds=' + encodeURIComponent(selectedIds);
var url2 = apiPopupUrl + '?cmd=POPUP&searchApiSpecYn=Y&searchPortalDisplayYn=Y'
+ '&selectedApiIds=' + encodeURIComponent(selectedIds);
var args = {};
showModal(url2, args, 1280, 860, function () {
var doc = args.self && args.self.document;
@@ -40,7 +40,7 @@
if ($('input[name=prptyName]').val() == "") {
alert("<%= localeMessage.getString("propertyDetail.checkRequired2") %>");
return false;
} else if ($('input[name=prpty2Val]').val() == "") {
} else if ($('[name=prpty2Val]').val() == "") {
alert("<%= localeMessage.getString("propertyDetail.checkRequired3") %>");
return false;
}
@@ -90,7 +90,19 @@
'설명',
'<%= localeMessage.getString("propertyDetail.delYn") %>'],
colModel: [{name: 'PRPTYNAME', width: 50, align: 'left', editable: true},
{name: 'PRPTY2VAL', width: 150, align: 'left', editable: true},
{
name: 'PRPTY2VAL',
width: 150,
align: 'left',
editable: true,
//다행(멀티라인) 값 편집 — 관련 사이트 목록처럼 줄 단위 표기하는 값 지원
edittype: 'textarea',
editoptions: {rows: 3, cols: 60},
//줄바꿈이 그리드에서도 그대로 보이도록
cellattr: function () {
return ' style="white-space:pre-line; word-break:break-all;"';
}
},
{name: 'PRPTYDESC', width: 200, align: 'left', editable: true, edittype: 'textarea', editoptions: {rows: 2}},
{
name: 'DELETEYN',
@@ -265,7 +277,7 @@
}
var data = new Object();
data["PRPTYNAME"] = $("input[name=prptyName]").val();
data["PRPTY2VAL"] = $("input[name=prpty2Val]").val();
data["PRPTY2VAL"] = $("[name=prpty2Val]").val();
data["PRPTYDESC"] = $("input[name=prptyDesc]").val();
var rows = $("#grid")[0].rows;
@@ -346,7 +358,8 @@
<tr>
<th><%= localeMessage.getString("propertyDetail.propertyValue") %>
</th>
<td colspan="3"><input type="text" name="prpty2Val"/>
<td colspan="3"><textarea name="prpty2Val" rows="3"
style="width:100%; box-sizing:border-box; resize:vertical;"></textarea>
</td>
</tr>
<tr>
@@ -222,9 +222,9 @@
<td><input type="text" name="searchTargetUrl" autocomplete="off" style="width:95%;"></td>
<th>발송기간</th>
<td>
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:40%; border:1px solid #ebebec;">
<input type="text" name="searchStartYYYYMMDD" id="startDatepicker" readonly="readonly" value="" size="10" style="width:100px; border:1px solid #ebebec;">
~
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:40%; border:1px solid #ebebec;">
<input type="text" name="searchEndYYYYMMDD" value="" id="endDatepicker" size="10" readonly="readonly" style="width:100px; border:1px solid #ebebec;">
<input type="hidden" name="searchStartDate" value="">
<input type="hidden" name="searchEndDate" value="">
</td>
@@ -601,7 +601,7 @@
<tbody>
<tr>
<th style="width:100px;">조회기간</th>
<td colspan="3">
<td style="width:400px">
<input type="text" name="searchStartDate" value="${param.searchStartDate}" style="width:100px;">
<input type="text" name="searchStartTime" value="${param.searchStartTime}" style="width:60px;">
~
@@ -609,6 +609,10 @@
<input type="text" name="searchEndTime" value="${param.searchEndTime}" style="width:60px;">
<span style="color:#888; font-size:12px; margin-left:10px;">(최대 1시간, 초과시 자동 조정)</span>
</td>
<th style="width:100px;">API명</th>
<td style="width:400px">
<input type="text" name="searchApiName" value="${param.searchApiName}">
</td>
</tr>
</tbody>
</table>
@@ -15,6 +15,12 @@
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script src="<c:url value="/addon/echarts/echarts.min.js"/>"></script>
<style>
/* 합계(footer) 행 높이를 본문 행 높이(23px)와 동일하게 맞춤 */
#gbox_grid .ui-jqgrid-ftable tr.footrow td {
height: 23px;
}
</style>
<script language="javascript">
var url = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.json"/>';
var url_view = '<c:url value="/onl/kjb/statistics/apiUseStatsMan.view"/>';
@@ -63,7 +69,7 @@
function search() {
var postData = getPostData("cmd", "LIST");
if (postData) {
$("#grid").setGridParam({ url: url, postData: postData, page: 1 }).trigger("reloadGrid");
$("#grid").setGridParam({ url: url, postData: postData }).trigger("reloadGrid");
}
}
@@ -198,19 +204,17 @@
{ name: 'maxRespTime', align: 'right', width: '90', formatter: numberFormatter, sortable: false }
],
jsonReader: { repeatitems: false },
pager: $('#pager'),
page: '${param.page}',
rowNum: '${rmsDefaultRowNum}',
autoheight: true,
height: 'auto',
height: 500,
autowidth: true,
viewrecords: true,
rowList: eval('[${rmsDefaultRowList}]'),
footerrow: true,
userDataOnFooter: true,
loadComplete: function(d) {
var colModel = $(this).getGridParam("colModel");
for (var i = 0; i < colModel.length; i++) {
$(this).setColProp(colModel[i].name, { sortable: false });
}
$(".ui-jqgrid-ftable td").css({ "font-weight": "bold", "background-color": "#f5f5f5" });
}
});
}
@@ -308,7 +312,6 @@
<div>
<table id="grid"></table>
<div id="pager"></div>
</div>
@@ -0,0 +1,140 @@
<%@ page language="java" contentType="text/html; charset=utf-8" %>
<%@ page import="java.io.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expires", "0");
%>
<html>
<head>
<title>인터페이스 배포</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<jsp:include page="/jsp/common/include/css.jsp"/>
<jsp:include page="/jsp/common/include/script.jsp"/>
<script language="javascript">
var url = '<c:url value="/onl/transaction/apim/apiInterfaceMan.json"/>';
var url_loader = '<c:url value="/loader.do"/>';
function loadList() {
$.ajax({
type: "POST",
url: url,
dataType: "json",
data: {cmd: 'LIST_DEPLOY_FILES'},
success: function (json) {
console.log('list', json)
if (json) {
$('#grid').jqGrid('clearGridData').jqGrid('setGridParam', {data: json.rows || []}).trigger('reloadGrid');
}
},
error: function (e) {
alert(e.responseText);
}
});
}
function callDeploy(filePath) {
var postData = [];
postData.push({name: "cmd", value: "interface"});
postData.push({name: "control", value: "uploadfull"});
postData.push({name: "filePath", value: filePath});
return new Promise(function (resolve, reject) {
$.ajax({
type: "POST",
url: url_loader,
data: postData,
success: function (response) {
resolve(response);
},
error: function (error) {
reject(error);
}
});
});
}
$(document).ready(function () {
$("#btn_close").click(function () {
window.close();
});
$('#grid').jqGrid({
datatype: "local",
colNames: ['업무그룹', '인터페이스', '생성일시', 'filePath'],
colModel: [
{name: 'bizCode', align: 'center', width: '80', sortable: true},
{name: 'interfaceId', align: 'left', sortable: true},
{name: 'createdDate', align: 'center', width: '130', sortable: true},
{name: 'filePath', hidden: true}
],
pager: $('#pager'),
rowNum: 20,
rowList: [10, 20, 50, 100],
autoheight: false,
height: 460,
width: 650,
autowidth: false,
viewrecords: true,
shrinkToFit: true,
multiselect: true,
multiboxonly: false
});
loadList();
$("#btn_deploy").click(async function () {
var rowIds = $('#grid').jqGrid('getGridParam', 'selarrrow');
if (rowIds.length === 0) {
alert('배포할 인터페이스를 선택하세요.');
return;
}
if (!confirm(rowIds.length + '건을 배포 하시겠습니까?')) {
return;
}
$("#btn_deploy").prop("disabled", true);
var successCount = 0;
var failedList = [];
for (var i = 0; i < rowIds.length; i++) {
var rowData = $("#grid").getRowData(rowIds[i]);
var target = rowData['bizCode'] + '/' + rowData['interfaceId'];
try {
var response = await callDeploy(rowData['filePath']);
if (response != null && response.includes('status=success')) {
successCount++;
} else {
failedList.push(target);
}
} catch (error) {
failedList.push(target);
}
}
$("#btn_deploy").prop("disabled", false);
var msg = '배포 완료 - 성공[' + successCount + ']건, 실패[' + failedList.length + ']건';
if (failedList.length > 0) {
msg += '\n실패목록 : ' + failedList.join(', ');
}
alert(msg);
});
});
</script>
</head>
<body>
<div class="popup_box">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_deploy" level="W"><i class="material-icons">cloud_upload</i> 배포
</button>
<button type="button" class="cssbtn" id="btn_close" level="R"><i class="material-icons">cancel</i> <%= localeMessage.getString("button.close") %>
</button>
</div>
<div class="title">배포 대상 인터페이스 목록</div>
<table id="grid"></table>
<div id="pager"></div>
</div>
</body>
</html>
@@ -1,5 +1,6 @@
<%@ page language="java" contentType="text/html; charset=utf-8" %>
<%@ page import="java.io.*" %>
<%@ page import="com.eactive.eai.common.util.SystemUtil" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ include file="/jsp/common/include/localemessage.jsp" %>
<%
@@ -246,6 +247,11 @@
window.alert('이관 대상 API 성공['+successCount+']건, 실패['+failedCount+']건 서버에 다운로드 완료');
});
$("#btn_deploy").click(function(){
var url2 = '<c:url value="/onl/transaction/apim/apiInterfaceMan.view"/>?cmd=DEPLOY_POPUP';
showModal2(url2, {}, 663, 600);
});
buttonControl();
// 25.11.25 파일 가져오기 할 때 중복 검사 기능추가.
@@ -380,7 +386,8 @@
</div><!-- end content_top -->
<div class="content_middle" id="content_middle">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_download" level="W" status="DETAIL"><i class="material-icons">cloud_download</i> 이관</button>
<button type="button" class="cssbtn" id="btn_download" level="W" status="DETAIL"><i class="material-icons">cloud_download</i> 이관</button>
<button type="button" class="cssbtn" id="btn_deploy" level="W" status="DETAIL"><i class="material-icons">cloud_upload</i> 배포</button>
<button type="button" class="cssbtn" id="btn_import" level="R" data-bs-toggle="modal"
data-bs-target="#uploadModal"><i
class="material-icons">upload</i> <%=localeMessage.getString("button.import")%>
@@ -396,16 +403,10 @@
<table class="search_condition" cellspacing=0;>
<tbody>
<tr>
<th style="width:180px;">업무구분명</th>
<td>
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}"></select>
</td>
<th style="width:180px;"><%= localeMessage.getString("eaiMessage.eaiSvcName")%></th>
<td>
<input type="text" name="searchEaiSvcName" value="${param.searchEaiSvcName}">
</td>
</tr>
<tr>
<th style="width:180px;"><%= localeMessage.getString("eaiMessage.eaiSvcDesc")%></th>
<td>
<input type="text" name="searchEaiSvcDesc" value="${param.searchEaiSvcDesc}">
@@ -415,6 +416,23 @@
<input type="text" name="searchApiFullPath" value="${param.searchApiFullPath}">
</td>
</tr>
<tr>
<th style="width:180px;">업무구분명</th>
<td>
<select name="searchEaiBzwkDstcd" value="${param.searchEaiBzwkDstcd}"></select>
</td>
<th style="width:180px;">상태</th>
<td>
<select name="searchStatusCode" style="width: 100%">
<option value="">전체</option>
<option value="N">정상</option>
<option value="D">지연</option>
<option value="E">장애</option>
<option value="C">점검</option>
</select>
</td>
<th></th><td></td>
</tr>
</tbody>
</table>
<div class="help-text" style="color:#666; font-size:12px; margin-top:5px;">(*) : 정렬가능항목</div>
@@ -646,7 +646,7 @@
const svcDesc = encodeURI(encodeURIComponent($('#eaiSvcDesc').val()));
const cnvsnDesc = encodeURI(encodeURIComponent(transformDesc));
var args = {title:transformName+' 수정', parent: true};
var url='<c:url value="/onl/admin/rule/transform2/transform2Man.view"/>';
var url='<c:url value="/onl/admin/rule/transformMan.view"/>';
url += '?cmd=DETAIL';
url += '&cnvsnName='+transformName;
url += '&eaiSvcName='+svcName;
@@ -793,7 +793,7 @@
function updateFirstLayout(newTransformName, transformDesc){
$.ajax({
type : "POST",
url: '<c:url value="/onl/admin/rule/transform2/transform2Man.json" />',
url: '<c:url value="/onl/admin/rule/transformMan.json" />',
data: { cmd: 'UPDATE',
gridData: '[]',
eaiSvcName: svcName,
@@ -824,7 +824,7 @@
};
$.ajax({
type : "POST",
url: '<c:url value="/onl/admin/rule/transform2/transform2Man.json" />',
url: '<c:url value="/onl/admin/rule/transformMan.json" />',
data: { cmd: 'INSERT',
gridData: '[]',
cnvsnType,
@@ -1608,8 +1608,10 @@
<button type="button" class="cssbtn" id="btn_json_export" level="R" status="DETAIL,NEW"><i class="material-icons">download</i> <%=localeMessage.getString("button.exportJson")%></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>
<button type="button" class="cssbtn" id="btn_modify" level="R" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button>
<!-- v1: 기존 API 스펙 (모달) — 기능 비교용 복원 -->
<button type="button" class="cssbtn" id="btn_api_spec" level="R" status="DETAIL"><i class="material-icons">description</i> API 스펙(삭제예정)</button>
<%-- v1: 기존 API 스펙 (모달). PTL_PROPERTY(Portal / djb.apispec.v1.enabled) 로 노출 제어. --%>
<c:if test="${apiSpecV1Enabled}">
<button type="button" class="cssbtn" id="btn_api_spec" level="R" status="DETAIL"><i class="material-icons">description</i> API 스펙</button>
</c:if>
<!-- v2: 신규 OpenAPI Spec (새 탭) -->
<button type="button" class="cssbtn" id="btn_openapi_spec" level="R" status="DETAIL"><i class="material-icons">api</i> OpenAPI Spec</button>
<button type="button" class="cssbtn" id="btn_previous" level="R" status="DETAIL,NEW"><i class="material-icons">arrow_back</i> <%= localeMessage.getString("button.previous") %></button>
@@ -222,63 +222,6 @@
$("input[name=searchEndHHMM]").val(endHHMM);
}
function guidEvent() {
var searchGuid = $("input[name=searchGuid]").val();
if (searchGuid.trim() == '')
return;
var searchTm = searchGuid.trim();
/*
var yy = searchTm.substr(13,4);
var mm = searchTm.substr(17,2);
var dd = searchTm.substr(19,2);
var hh = searchTm.substr(21,2);
var MM = searchTm.substr(23,2);
*/
var yy = searchTm.substr(3,4);
var mm = searchTm.substr(7,2);
var dd = searchTm.substr(9,2);
var hh = searchTm.substr(11,2);
var MM = searchTm.substr(13,2);
var ms = Date.UTC(yy,mm-1,dd,hh,MM,00);
var date = new Date(ms);
var startYYYYMMDD;
var startHHMM;
var endYYYYMMDD;
var endHHMM;
//Local Time Setting
date.setHours(date.getHours() - 9);
//5분 간격
date.setMinutes(date.getMinutes() - 5);
startYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
startHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
date.setMinutes(date.getMinutes() + 10);
endYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
endHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
//추가 코드 입력 2012.05.16 권세환 (일자가 바뀔경우 시간 간격을 +-5분 -> +-1분)
if (startYYYYMMDD.substr(6, 2) != endYYYYMMDD.substr(6, 2)) {
date.setMinutes(date.getMinutes() - 6);
startYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
startHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
date.setMinutes(date.getMinutes() + 2);
endYYYYMMDD = date.getFullYear() + right("0" + (date.getMonth() + 1), 2) + right("0" + (date.getDate()), 2);
endHHMM = right("0" + date.getHours(), 2) + right("0" + date.getMinutes(), 2) + "00";
}
$("input[name=searchStartYYYYMMDD]").val(startYYYYMMDD);
$("input[name=searchEndYYYYMMDD]").val(endYYYYMMDD);
$("input[name=searchStartHHMM]").val(startHHMM);
$("input[name=searchEndHHMM]").val(endHHMM);
}
function timeStampFormat2(cellvalue, options, rowObject) {
if (cellvalue == null || cellvalue.length < 17)
return '';
@@ -481,7 +424,6 @@
//$("#grid").jqGrid('setFrozenColumns');
$("#btn_search").click(function () {
$("input[name=searchGuid]").blur();
detail();
getTransactionCounts();
});
@@ -551,12 +493,6 @@
return false;
});
//GUID
$("input[name=searchGuid]").blur(function (event) {
guidEvent();
});
buttonControl();
var local = localStorage.getItem("guid");
@@ -701,7 +637,7 @@
<td colspan="3"><input type="text" name="searchEaiSvcSerno" value="${param.searchEaiSvcSerno}"></td>
<th><%=localeMessage.getString("tracking.keymgtmsgctnt")%></th>
<td colspan="2"><input type="text" name="searchKeyMgtMsgCtnt" value="${param.searchKeyMgtMsgCtnt}"></td>
<td colspan="2"><input type="text" name="searchGuid" value="${param.searchGuid}" ></td>
<%-- <th><%=localeMessage.getString("tracking.orgGUID")%></th>
<td colspan="4"><input type="text" name="searchGuid" value="" maxlength="<%=DbTrackingService.GUID_LENGTH%>"></td> --%>
@@ -376,7 +376,7 @@
const value = bodyCodeEditor.getValue();
let prettyValue = value;
if(syntax === 'JSN'){
prettyValue = vkbeautify.json(value);
prettyValue = preserveExponential(value); //vkbeautify.json(value);
}else if(syntax === 'XML'){
prettyValue = vkbeautify.xml(value).trim();
}
@@ -407,6 +407,19 @@
}
});
}
function preserveExponential(jsonString) {
// 1. 지수 형태의 숫자(예: 1e+21, 5.43e-7)를 찾아 문자열 ""로 감쌉니다.
// JSON의 key 뒤에 오는 value 위치의 지수 표기법을 매칭합니다.
let protectedJson = jsonString.replace(/:\s*([+-]?\d+\.?\d*e[+-]?\d+)/gi, ': "$1"');
// 2. 보호된 상태로 vkbeautify를 실행하여 예쁘게 정렬합니다.
let prettyJson = vkbeautify.json(protectedJson);
// 3. (선택사항) 만약 최종 결과물에서 반드시 큰따옴표("")를 다시 제거해야 한다면 아래 주석을 해제하세요.
prettyJson = prettyJson.replace(/:\s*"([+-]?\d+\.?\d*e[+-]?\d+)"/gi, ': $1');
return prettyJson;
}
function doChangeSyntax(syntax){
if(syntax === 'JSN'){
$('#btn_pretty_body').css('display', 'block');
@@ -789,6 +802,35 @@ $(document).ready(function() {
$("html, hbody").css("overflow-x","scroll");
}
function copyTextToClipboard(text, successMessage){
if (typeof(navigator.clipboard) == 'undefined'){
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
var successful = false;
try{
successful = document.execCommand('copy');
}catch(err){
}
document.body.removeChild(textArea);
if (successful){
alert(successMessage);
}else{
prompt("Ctrl+C 를 눌러 클립보드로 복사하세요.", text);
}
}else{
navigator.clipboard.writeText(text).then(function(){
alert(successMessage);
}).catch(function(){
prompt("클립보드 복사에 실패했습니다. Ctrl+C 를 눌러 복사하세요.", text);
});
}
}
$('#headerToClipboard').click(function(){
if($('#grid1').get(0) == null || $('#grid1').get(0).jexcel == null){
return;
@@ -800,13 +842,11 @@ $(document).ready(function() {
paddedValues += row[5].padEnd(row[4], " ");
}
navigator.clipboard.writeText(paddedValues);//클립보드로 복사
alert('헤더값이 복사되었습니다.')
copyTextToClipboard(paddedValues, '헤더값이 복사되었습니다.');
});
$('#messageToClipboard').click(function() {
navigator.clipboard.writeText(fullMessageData);//클립보드로 복사
alert('전체 전문 복사되었습니다.')
copyTextToClipboard(fullMessageData, '전체 전문 복사되었습니다.');
});
$('#btn_pretty_print').click(function () {
@@ -1682,7 +1682,7 @@ $(document).ready(function () {
var args = new Object();
var url = '<c:url value="/onl/admin/rule/layoutMan.view" />';
url += "?cmd=DETAILPOPUP";
url += '&loutName='+layoutName;
url += '&searchLoutName='+layoutName;
url += '&pop=true';
showModal2(url,args,890,940,'','scroll:on');
+343
View File
@@ -0,0 +1,343 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %><%@
page import="org.quartz.*,
org.quartz.impl.matchers.GroupMatcher,
org.springframework.web.context.WebApplicationContext,
org.springframework.web.context.support.WebApplicationContextUtils,
java.util.*,
java.text.SimpleDateFormat"
%><%!
private String fmt(Date date) {
if (date == null) return "-";
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
}
private String relative(Date date) {
if (date == null) return "";
long diff = date.getTime() - System.currentTimeMillis();
String prefix = diff >= 0 ? "+" : "-";
long abs = Math.abs(diff) / 1000;
if (abs < 60) return prefix + abs + "s";
if (abs < 3600) return prefix + (abs / 60) + "m " + (abs % 60) + "s";
return prefix + (abs / 3600) + "h " + ((abs % 3600) / 60) + "m";
}
private String stateClass(Trigger.TriggerState state) {
if (state == Trigger.TriggerState.NORMAL) return "state-normal";
if (state == Trigger.TriggerState.PAUSED) return "state-paused";
if (state == Trigger.TriggerState.BLOCKED) return "state-blocked";
if (state == Trigger.TriggerState.ERROR) return "state-error";
return "";
}
private Map<String, Object> buildTriggerInfo(Scheduler scheduler, Trigger trigger) throws Exception {
Map<String, Object> info = new LinkedHashMap<>();
Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
info.put("triggerName", trigger.getKey().getName());
info.put("triggerGroup", trigger.getKey().getGroup());
info.put("state", state.name());
info.put("stateClass", stateClass(state));
info.put("prevFire", fmt(trigger.getPreviousFireTime()));
info.put("prevFireRel", relative(trigger.getPreviousFireTime()));
info.put("nextFire", fmt(trigger.getNextFireTime()));
info.put("nextFireRel", relative(trigger.getNextFireTime()));
info.put("priority", trigger.getPriority());
if (trigger instanceof CronTrigger) {
info.put("type", "Cron");
info.put("cronExp", ((CronTrigger) trigger).getCronExpression());
} else if (trigger instanceof SimpleTrigger) {
SimpleTrigger st = (SimpleTrigger) trigger;
info.put("type", "Simple");
info.put("cronExp", "interval=" + st.getRepeatInterval() + "ms, repeat=" +
(st.getRepeatCount() == SimpleTrigger.REPEAT_INDEFINITELY ? "INF" : st.getRepeatCount()));
} else {
info.put("type", trigger.getClass().getSimpleName());
info.put("cronExp", "-");
}
if (trigger.getStartTime() != null) info.put("startTime", fmt(trigger.getStartTime()));
if (trigger.getEndTime() != null) info.put("endTime", fmt(trigger.getEndTime()));
return info;
}
private List<Map<String, Object>> getJobList(Scheduler scheduler) throws Exception {
List<Map<String, Object>> jobs = new ArrayList<>();
for (String group : scheduler.getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group))) {
JobDetail detail = scheduler.getJobDetail(jobKey);
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
Map<String, Object> job = new LinkedHashMap<>();
job.put("name", jobKey.getName());
job.put("group", jobKey.getGroup());
job.put("jobClass", detail.getJobClass().getSimpleName());
job.put("disallowConcurrent", detail.isConcurrentExectionDisallowed());
StringBuilder dataStr = new StringBuilder();
for (String key : detail.getJobDataMap().getKeys()) {
if (dataStr.length() > 0) dataStr.append(", ");
dataStr.append(key).append("=").append(detail.getJobDataMap().getString(key));
}
job.put("jobData", dataStr.toString());
job.put("triggerCount", triggers.size());
List<Map<String, Object>> triggerList = new ArrayList<>();
for (Trigger trigger : triggers) {
triggerList.add(buildTriggerInfo(scheduler, trigger));
}
job.put("triggerList", triggerList);
if (!triggers.isEmpty()) {
Trigger trigger = triggers.get(0);
Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
job.put("state", state.name());
job.put("stateClass", stateClass(state));
job.put("prevFire", fmt(trigger.getPreviousFireTime()));
job.put("prevFireRel", relative(trigger.getPreviousFireTime()));
job.put("nextFire", fmt(trigger.getNextFireTime()));
job.put("nextFireRel", relative(trigger.getNextFireTime()));
if (trigger instanceof CronTrigger) {
job.put("cronExp", ((CronTrigger) trigger).getCronExpression());
} else {
job.put("cronExp", "-");
}
} else {
job.put("state", "NO_TRIGGER");
job.put("stateClass", "state-error");
job.put("prevFire", "-");
job.put("prevFireRel", "");
job.put("nextFire", "-");
job.put("nextFireRel", "");
job.put("cronExp", "-");
}
jobs.add(job);
}
}
jobs.sort(Comparator.comparing(m -> (String) m.get("name")));
return jobs;
}
%><%
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
Scheduler scheduler = null;
Scheduler clusteredScheduler = null;
String error = null;
try {
scheduler = (Scheduler) ctx.getBean("scheduler");
} catch (Exception e) {
error = "scheduler bean not found: " + e.getMessage();
}
try {
clusteredScheduler = (Scheduler) ctx.getBean("clusteredScheduler");
} catch (Exception e) {
// clusteredScheduler is optional
}
// 자동 새로고침 간격 (초), 기본 10초
String refreshParam = request.getParameter("refresh");
int refreshSec = 10;
if (refreshParam != null) {
try { refreshSec = Integer.parseInt(refreshParam); } catch (NumberFormatException e) { /* ignore */ }
if (refreshSec < 1) refreshSec = 1;
}
boolean autoRefresh = refreshParam != null;
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<% if (autoRefresh) { %>
<meta http-equiv="refresh" content="<%= refreshSec %>;url=quartzStatus.jsp?refresh=<%= refreshSec %>">
<% } %>
<title>Quartz Scheduler Status</title>
<style>
body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background: #f5f5f5; color: #333; }
h1 { font-size: 20px; margin-bottom: 5px; }
h2 { font-size: 16px; color: #555; margin: 20px 0 8px 0; border-bottom: 2px solid #ddd; padding-bottom: 4px; }
.info { font-size: 13px; color: #666; margin-bottom: 15px; }
table { width: 100%%; border-collapse: collapse; font-size: 13px; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; }
th { background: #4a5568; color: #fff; padding: 8px 10px; text-align: left; font-weight: 500; }
td { padding: 7px 10px; border-bottom: 1px solid #eee; }
tr:hover td { background: #f7fafc; }
.state-normal { color: #38a169; font-weight: bold; }
.state-paused { color: #d69e2e; font-weight: bold; }
.state-blocked { color: #e53e3e; font-weight: bold; }
.state-error { color: #e53e3e; font-weight: bold; }
.mono { font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; }
.rel { color: #718096; font-size: 11px; margin-left: 4px; }
.badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 11px; }
.badge-yes { background: #c6f6d5; color: #276749; }
.badge-no { background: #fed7d7; color: #9b2c2c; }
.scheduler-meta { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 10px; }
.scheduler-meta span { font-size: 13px; }
.scheduler-meta strong { color: #4a5568; }
.error { color: #e53e3e; padding: 10px; background: #fff5f5; border: 1px solid #fc8181; border-radius: 4px; }
.toolbar { font-size: 12px; color: #718096; }
.toolbar a { color: #4299e1; text-decoration: none; margin: 0 4px; }
.toolbar a:hover { text-decoration: underline; }
.toolbar .active { font-weight: bold; color: #2b6cb0; background: #ebf4ff; padding: 2px 6px; border-radius: 3px; }
.auto-badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 11px; background: #bee3f8; color: #2a4365; margin-left: 6px; }
.trigger-toggle { cursor: pointer; user-select: none; }
.trigger-toggle:hover td { background: #edf2f7; }
.trigger-row { display: none; }
.trigger-row.open { display: table-row; }
.trigger-row td { background: #f7fafc; padding: 5px 10px 5px 30px; border-bottom: 1px solid #e2e8f0; font-size: 12px; }
.trigger-detail-table { width: 100%; border-collapse: collapse; font-size: 12px; margin: 0; }
.trigger-detail-table th { background: #e2e8f0; color: #4a5568; padding: 5px 8px; font-weight: 500; font-size: 11px; }
.trigger-detail-table td { padding: 4px 8px; border-bottom: 1px solid #e2e8f0; }
.trigger-detail-table tr:last-child td { border-bottom: none; }
.toggle-icon { display: inline-block; width: 16px; font-size: 11px; color: #718096; transition: transform 0.15s; }
.toggle-icon.open { transform: rotate(90deg); }
.trigger-count { font-size: 11px; color: #718096; font-weight: normal; margin-left: 4px; }
</style>
</head>
<body>
<h1>Quartz Scheduler Status
<% if (autoRefresh) { %><span class="auto-badge">Auto <%= refreshSec %>s</span><% } %>
</h1>
<p class="info">
Instance: <strong><%= System.getProperty("inst.Name", "N/A") %></strong>
| Server Time: <strong><%= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) %></strong>
</p>
<p class="toolbar">
Auto Refresh:
<% if (!autoRefresh) { %><span class="active">OFF</span><% } else { %><a href="quartzStatus.jsp">OFF</a><% } %>
|
<% int[] intervals = {5, 10, 30, 60}; %>
<% for (int iv : intervals) { %>
<% if (autoRefresh && refreshSec == iv) { %><span class="active"><%= iv %>s</span><% } else { %><a href="quartzStatus.jsp?refresh=<%= iv %>"><%= iv %>s</a><% } %>
<% } %>
</p>
<% if (error != null) { %>
<div class="error"><%= error %></div>
<% } %>
<% if (scheduler != null) { %>
<h2>Scheduler: <%= scheduler.getSchedulerName() %> (RAM)</h2>
<div class="scheduler-meta">
<span><strong>Status:</strong> <span class="<%= scheduler.isStarted() ? "state-normal" : "state-error" %>"><%= scheduler.isStarted() ? "STARTED" : "STOPPED" %></span></span>
<span><strong>Instance ID:</strong> <%= scheduler.getSchedulerInstanceId() %></span>
<span><strong>Job Count:</strong> <%= getJobList(scheduler).size() %></span>
</div>
<table>
<tr>
<th>Job Name</th>
<th>Job Class</th>
<th>Cron</th>
<th>State</th>
<th>Concurrent</th>
<th>Prev Fire</th>
<th>Next Fire</th>
<th>Job Data</th>
</tr>
<% int jobIdx = 0; for (Map<String, Object> job : getJobList(scheduler)) { String rowId = "s_" + jobIdx; List<Map<String, Object>> tList = (List<Map<String, Object>>) job.get("triggerList"); %>
<tr class="trigger-toggle" onclick="toggleTriggers('<%= rowId %>')">
<td><span class="toggle-icon" id="icon_<%= rowId %>">&#9654;</span><strong><%= job.get("name") %></strong><span class="trigger-count">(<%= job.get("triggerCount") %> triggers)</span></td>
<td class="mono"><%= job.get("jobClass") %></td>
<td class="mono"><%= job.get("cronExp") %></td>
<td class="<%= job.get("stateClass") %>"><%= job.get("state") %></td>
<td><span class="badge <%= (Boolean)job.get("disallowConcurrent") ? "badge-yes" : "badge-no" %>">
<%= (Boolean)job.get("disallowConcurrent") ? "NO (protected)" : "YES (allowed)" %></span></td>
<td class="mono"><%= job.get("prevFire") %><span class="rel"><%= job.get("prevFireRel") %></span></td>
<td class="mono"><%= job.get("nextFire") %><span class="rel"><%= job.get("nextFireRel") %></span></td>
<td class="mono"><%= job.get("jobData") %></td>
</tr>
<tr class="trigger-row" id="triggers_<%= rowId %>">
<td colspan="8" style="padding: 6px 10px 6px 30px;">
<% if (tList.isEmpty()) { %>
<em style="color:#a0aec0;">No triggers</em>
<% } else { %>
<table class="trigger-detail-table">
<tr><th>Trigger Name</th><th>Group</th><th>Type</th><th>Expression / Interval</th><th>State</th><th>Priority</th><th>Prev Fire</th><th>Next Fire</th></tr>
<% for (Map<String, Object> t : tList) { %>
<tr>
<td><%= t.get("triggerName") %></td>
<td><%= t.get("triggerGroup") %></td>
<td><%= t.get("type") %></td>
<td class="mono"><%= t.get("cronExp") %></td>
<td class="<%= t.get("stateClass") %>"><%= t.get("state") %></td>
<td><%= t.get("priority") %></td>
<td class="mono"><%= t.get("prevFire") %><span class="rel"><%= t.get("prevFireRel") %></span></td>
<td class="mono"><%= t.get("nextFire") %><span class="rel"><%= t.get("nextFireRel") %></span></td>
</tr>
<% } %>
</table>
<% } %>
</td>
</tr>
<% jobIdx++; } %>
</table>
<% } %>
<% if (clusteredScheduler != null) { %>
<h2>Clustered Scheduler: <%= clusteredScheduler.getSchedulerName() %> (DB)</h2>
<div class="scheduler-meta">
<span><strong>Status:</strong> <span class="<%= clusteredScheduler.isStarted() ? "state-normal" : "state-error" %>"><%= clusteredScheduler.isStarted() ? "STARTED" : "STOPPED" %></span></span>
<span><strong>Instance ID:</strong> <%= clusteredScheduler.getSchedulerInstanceId() %></span>
<span><strong>Job Count:</strong> <%= getJobList(clusteredScheduler).size() %></span>
</div>
<table>
<tr>
<th>Job Name</th>
<th>Job Class</th>
<th>Cron</th>
<th>State</th>
<th>Concurrent</th>
<th>Prev Fire</th>
<th>Next Fire</th>
<th>Job Data</th>
</tr>
<% int cJobIdx = 0; for (Map<String, Object> job : getJobList(clusteredScheduler)) { String rowId = "c_" + cJobIdx; List<Map<String, Object>> tList = (List<Map<String, Object>>) job.get("triggerList"); %>
<tr class="trigger-toggle" onclick="toggleTriggers('<%= rowId %>')">
<td><span class="toggle-icon" id="icon_<%= rowId %>">&#9654;</span><strong><%= job.get("name") %></strong><span class="trigger-count">(<%= job.get("triggerCount") %> triggers)</span></td>
<td class="mono"><%= job.get("jobClass") %></td>
<td class="mono"><%= job.get("cronExp") %></td>
<td class="<%= job.get("stateClass") %>"><%= job.get("state") %></td>
<td><span class="badge <%= (Boolean)job.get("disallowConcurrent") ? "badge-yes" : "badge-no" %>">
<%= (Boolean)job.get("disallowConcurrent") ? "NO (protected)" : "YES (allowed)" %></span></td>
<td class="mono"><%= job.get("prevFire") %><span class="rel"><%= job.get("prevFireRel") %></span></td>
<td class="mono"><%= job.get("nextFire") %><span class="rel"><%= job.get("nextFireRel") %></span></td>
<td class="mono"><%= job.get("jobData") %></td>
</tr>
<tr class="trigger-row" id="triggers_<%= rowId %>">
<td colspan="8" style="padding: 6px 10px 6px 30px;">
<% if (tList.isEmpty()) { %>
<em style="color:#a0aec0;">No triggers</em>
<% } else { %>
<table class="trigger-detail-table">
<tr><th>Trigger Name</th><th>Group</th><th>Type</th><th>Expression / Interval</th><th>State</th><th>Priority</th><th>Prev Fire</th><th>Next Fire</th></tr>
<% for (Map<String, Object> t : tList) { %>
<tr>
<td><%= t.get("triggerName") %></td>
<td><%= t.get("triggerGroup") %></td>
<td><%= t.get("type") %></td>
<td class="mono"><%= t.get("cronExp") %></td>
<td class="<%= t.get("stateClass") %>"><%= t.get("state") %></td>
<td><%= t.get("priority") %></td>
<td class="mono"><%= t.get("prevFire") %><span class="rel"><%= t.get("prevFireRel") %></span></td>
<td class="mono"><%= t.get("nextFire") %><span class="rel"><%= t.get("nextFireRel") %></span></td>
</tr>
<% } %>
</table>
<% } %>
</td>
</tr>
<% cJobIdx++; } %>
</table>
<% } %>
<script>
function toggleTriggers(rowId) {
var row = document.getElementById('triggers_' + rowId);
var icon = document.getElementById('icon_' + rowId);
if (row.classList.contains('open')) {
row.classList.remove('open');
icon.classList.remove('open');
} else {
row.classList.add('open');
icon.classList.add('open');
}
}
</script>
</body>
</html>
+2 -3
View File
@@ -87,9 +87,8 @@ dependencies {
//implementation project(':kjb-safedb')
//implementation project(":eapim-admin-djb")
/* Custom Libs (damo-manager.jar 는 Tomcat lib 가 런타임 제공 → compileOnly, WAR 미포함) */
implementation fileTree(dir: 'libs', include: ['*.jar'], exclude: ['damo-manager.jar'])
compileOnly files('libs/damo-manager.jar')
/* Custom Libs */
implementation fileTree(dir: 'libs', include: ['*.jar'])
//implementation "org.dom4j:dom4j:2.1.3"
//implementation "com.rabbitmq:amqp-client:3.6.6"
Binary file not shown.
@@ -259,6 +259,7 @@ public interface MonitoringContext {
public static final String RMS_AUTO_LOGOUT_TIMEOUT = "rms.auto.logout.timeout";
// 웹훅
public static final String RMS_WEBHOOK_ENABLED = "rms.webhook.enabled";
public static final String RMS_WEBHOOK_REVERSE_PROXY_URL = "rms.webhook.reverse_proxy.url";
public static final String RMS_WEBHOOK_RETRY_COUNT = "rms.webhook.retry_count";
public static final String RMS_WEBHOOK_RETRY_TIME = "rms.webhook.retry_time";
@@ -269,6 +270,9 @@ public interface MonitoringContext {
// 최대 로그인 실패 허용횟수 (0이면 계정 잠금 미적용)
public static final String RMS_PASSWORD_FAIL_COUNT = "rms.password.fail.count";
// API 상태변화 스윙챗 발송여부
public static final String DJB_UMS_MESSENGER_APIMONITOR_ENABLED = "djb.ums.messenger.api-monitor.enabled";
@@ -20,22 +20,6 @@ public class ClusteredSchedulerConfig {
@Autowired @Qualifier("MONITORING")
private DataSourceType monitoringDatasource;
/*
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory {
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object job = super.createJobInstance(bundle);
applicationContext.getAutowireCapableBeanFactory().autowireBean(job);
return job;
}
}
@Bean
public SpringBeanJobFactory springBeanJobFactory() {
return new AutowiringSpringBeanJobFactory();
}
*/
@Bean(name = "clusteredScheduler")
public SchedulerFactoryBean clusteredScheduler() {
@@ -55,7 +39,7 @@ public class ClusteredSchedulerConfig {
quartzProperties.put("org.quartz.threadPool.threadCount", "5");
quartzProperties.put("org.quartz.threadPool.threadPriority", "5");
quartzProperties.put("org.quartz.jobStore.misfireThreshold", "60000");
quartzProperties.put("org.quartz.jobStore.misfireThreshold", "10000");
quartzProperties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
quartzProperties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
quartzProperties.put("org.quartz.jobStore.useProperties", "false");
@@ -81,7 +65,11 @@ public class ClusteredSchedulerConfig {
factory.setQuartzProperties(quartzProperties);
factory.setAutoStartup(true);
factory.setSchedulerName("EMS_ClusteredScheduler");
// factory.setJobFactory(springBeanJobFactory());
AutoWiringSpringBeanJobFactory jobFactory =
new AutoWiringSpringBeanJobFactory(applicationContext.getAutowireCapableBeanFactory());
jobFactory.setApplicationContext(applicationContext);
factory.setJobFactory(jobFactory);
factory.setApplicationContextSchedulerContextKey("applicationContext");
factory.setApplicationContext(applicationContext);
@@ -1,6 +1,7 @@
package com.eactive.eai.rms.common.vo;
import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Page;
@@ -10,6 +11,8 @@ public class GridResponse<T> {
private Integer page;
private Long records;
private List<T> rows;
/** jqGrid footerrow + userDataOnFooter 용 하단 합계행 데이터 (colModel name -> value) */
private Map<String, Object> userdata;
public GridResponse() {
}
@@ -54,6 +57,14 @@ public class GridResponse<T> {
this.rows = rows;
}
public Map<String, Object> getUserdata() {
return userdata;
}
public void setUserdata(Map<String, Object> userdata) {
this.userdata = userdata;
}
public GridResponse(List<T> rows) {
this.rows = rows;
this.total = 1;
@@ -20,6 +20,6 @@ public class WebhookReqEventId implements Serializable {
private String webhookReqId;
@Column(name = "EVENT_TYPE", nullable = false, length = 50)
@Comment("이벤트 유형 (CONTROL_START, ERROR_START 등)")
@Comment("이벤트 유형 (CHECK_START, ERROR_START 등)")
private String eventType;
}
@@ -13,7 +13,10 @@ import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository;
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.QApiGroupApi;
import com.eactive.eai.data.entity.onl.authserver.QScopeEntity;
import com.eactive.eai.data.entity.onl.logger.QStatLog;
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
@@ -31,6 +34,9 @@ import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
@Service
@@ -38,8 +44,14 @@ import com.querydsl.jpa.impl.JPAQuery;
@RequiredArgsConstructor
public class EAIMessageService extends AbstractDataService<EAIMessageEntity, String, EAIMessageRepository> {
/** Oracle IN 절 표현식 개수 상한. 넘기면 ORA-01795 라 나눠 OR 로 잇는다 */
private static final int ORACLE_IN_LIMIT = 1000;
private final UserBusinessService userBusinessService;
/** EMSAPP(PTL_API_SPEC_INFO) 조회용. 다른 스키마라 EAI 메시지 쿼리에 조인할 수 없다 */
private final ApiSpecInfoRepository apiSpecInfoRepository;
public Page<Tuple> selectList(EAIMessageUISearch eaiMessageUISearch, Pageable pageable, String sortname, String sortorder) {
QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity;
@@ -115,6 +127,7 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
return getJPAQueryFactory()
.select(qeaiMessageEntity
, qServiceMessageEntity.psvintfacdsticname
, qServiceMessageEntity.psvsysadptrbzwkgroupname
, qRouting.nonmotivrouturiname
, qStdInfo.apifullpath
, qStdInfo.bzwksvckeyname
@@ -136,8 +149,8 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
QEAIMessageEntity qeaiMessageEntity = QEAIMessageEntity.eAIMessageEntity;
QServiceMessageEntity qServiceMessageEntity = QServiceMessageEntity.serviceMessageEntity;
QRouting qRouting = QRouting.routing;
QStandardMessageInfo qStdInfo = QStandardMessageInfo.standardMessageInfo;
QApiStatus qApiStatus = QApiStatus.apiStatus;
BooleanBuilder predicate = new BooleanBuilder();
@@ -154,11 +167,10 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchEaiSvcDesc())) {
predicate.and(qeaiMessageEntity.eaisvcdesc.containsIgnoreCase(eaiMessageUISearch.getSearchEaiSvcDesc()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchSvcMotivUsedStcd())) {
predicate.and(qeaiMessageEntity.svcmotivusedstcd.eq(eaiMessageUISearch.getSearchSvcMotivUsedStcd()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchFlowControl())) {
predicate.and(qeaiMessageEntity.flowctrlroutname.eq(eaiMessageUISearch.getSearchFlowControl()));
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchGstatSysAdptrBzwkGroupName())) {
predicate
.and(qeaiMessageEntity.gstatsysadptrbzwkgroupname
.containsIgnoreCase(eaiMessageUISearch.getSearchGstatSysAdptrBzwkGroupName()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchTranType())) {
predicate.and(qeaiMessageEntity.trantype.eq(eaiMessageUISearch.getSearchTranType()));
@@ -178,18 +190,11 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
predicate.and(qeaiMessageEntity.refkey.containsIgnoreCase(eaiMessageUISearch.getSearchRefKey()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchQueueName())) {
predicate
.and(qeaiMessageEntity.flowctrlroutname.eq(qRouting.routname))
.and(qRouting.nonmotivrouturiname.containsIgnoreCase(eaiMessageUISearch.getSearchQueueName()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchPsvintfacdsticName())) {
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchPsvSysAdptrBzwkGroupName())) {
predicate
.and(qServiceMessageEntity.id.svcprcssno.eq(1))
.and(qServiceMessageEntity.psvintfacdsticname
.containsIgnoreCase(eaiMessageUISearch.getSearchPsvintfacdsticName()));
.and(qServiceMessageEntity.psvsysadptrbzwkgroupname
.containsIgnoreCase(eaiMessageUISearch.getSearchPsvSysAdptrBzwkGroupName()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchApiFullPath())) {
@@ -197,9 +202,61 @@ public class EAIMessageService extends AbstractDataService<EAIMessageEntity, Str
.and(qStdInfo.apifullpath.contains(eaiMessageUISearch.getSearchApiFullPath()));
}
if (StringUtils.isNotBlank(eaiMessageUISearch.getSearchStatusCode())) {
String statusCode = eaiMessageUISearch.getSearchStatusCode();
if ("N".equals(statusCode)) {
// 정상 : API_STATUS 에 코드가 없거나(join 안 됨) E/D/C 가 아닌 경우
predicate.and(qApiStatus.statusCode.isNull().or(qApiStatus.statusCode.notIn("E", "D", "C")));
} else {
predicate.and(qApiStatus.statusCode.eq(statusCode));
}
}
if ("Y".equals(eaiMessageUISearch.getSearchApiSpecYn())) {
predicate.and(apiSpecRegisteredExpression(qeaiMessageEntity.eaisvcname));
}
if ("Y".equals(eaiMessageUISearch.getSearchPortalDisplayYn())) {
predicate.and(qeaiMessageEntity.eaisvcname.in(displayedGroupApiIdSubQuery()));
}
return predicate;
}
/**
* OpenAPI Spec 등록 여부 조건.
*
* <p>PTL_API_SPEC_INFO 는 EMSAPP 스키마라 TSEAIHE01 쿼리에 조인하면 ORA-00942 다. 스펙이 있는
* API ID 를 EMS 커넥션에서 따로 읽어 IN 절로 넘긴다.</p>
*/
private BooleanExpression apiSpecRegisteredExpression(StringPath eaiSvcNamePath) {
List<String> specApiIds = apiSpecInfoRepository.findAllApiIds();
if (specApiIds.isEmpty()) {
// in(빈 리스트) 는 JPQL 이 깨지므로 항상 거짓인 조건으로 대체한다
return Expressions.ONE.eq(Expressions.TWO);
}
BooleanExpression expression = null;
for (int from = 0; from < specApiIds.size(); from += ORACLE_IN_LIMIT) {
int to = Math.min(from + ORACLE_IN_LIMIT, specApiIds.size());
BooleanExpression chunk = eaiSvcNamePath.in(specApiIds.subList(from, to));
expression = (expression == null) ? chunk : expression.or(chunk);
}
return expression;
}
/** 노출 중인(display_yn='1') API 그룹에 편성된 API ID. API_GROUP 은 EAI 메시지와 같은 스키마다 */
private JPQLQuery<String> displayedGroupApiIdSubQuery() {
QApiGroupApi qApiGroupApi = QApiGroupApi.apiGroupApi;
QApiGroup qApiGroup = QApiGroup.apiGroup;
return JPAExpressions
.select(qApiGroupApi.id.apiId)
.from(qApiGroupApi)
.join(qApiGroupApi.apiGroup, qApiGroup)
.where(qApiGroup.displayYn.eq("1"));
}
public Page<RoutingEaiMsgDto> findAll(Pageable pageable, RoutingEaiMsgSearch routingEaiMsgSearch) {
List<RoutingEaiMsgDto> result = routingEaiMsgDtoList(pageable, routingEaiMsgSearch);
@@ -8,13 +8,11 @@ public class EAIMessageUISearch {
private String searchEaiSvcName;
private String searchEaiBzwkDstcd;
private String searchEaiSvcDesc;
private String searchSvcMotivUsedStcd;
private String searchFlowControl;
private String searchGstatSysAdptrBzwkGroupName;
private String searchTranType;
private String searchQueueName;
private String rmsMenuAuth;
private String searchPsvintfacdsticName;
private String searchPsvSysAdptrBzwkGroupName;
private String searchExtBizCd;
private String tranChangeSim;
@@ -22,4 +20,13 @@ public class EAIMessageUISearch {
private String searchApiFullPath;
/** API_STATUS.STATUS_CODE (E:장애, D:지연, C:점검). "N" 이면 정상(코드 없음)만 조회 */
private String searchStatusCode;
/** "Y" 면 PTL_API_SPEC_INFO 에 OpenAPI Spec 이 등록된 API 만 조회 */
private String searchApiSpecYn;
/** "Y" 면 노출 중인(display_yn='1') API 그룹에 편성된 API 만 조회 */
private String searchPortalDisplayYn;
}
@@ -0,0 +1,76 @@
package com.eactive.eai.rms.data.entity.onl.layoutsync.history;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import org.hibernate.annotations.Comment;
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter16;
import com.eactive.eai.data.entity.AbstractEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
@Entity
@Table(name = "tseaitr10")
@org.hibernate.annotations.Table(appliesTo = "tseaitr10", comment = "레이아웃 연동 이력")
public class LayoutSyncHistoryEntity extends AbstractEntity<Long> implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@EqualsAndHashCode.Include
@Column(nullable = false)
@Comment("일련번호")
private Long seqno;
@Column(length = 36)
@Comment("연동로그처리일련번호")
private String logprcssseqno;
@Column(length = 100)
@Comment("서비스명")
private String servicename;
@Column(length = 20)
@Comment("커맨드")
private String command;
@Column(length = 50)
@Comment("레이아웃 명")
private String loutname;
@Lob
@Comment("수신 데이터")
private String recvdata;
@Column(length = 16)
@Comment("연동 시각")
@Convert(converter = LocalDateTimeToStringConverter16.class)
private LocalDateTime recvamndhms;
@Column(length = 1)
@Comment("처리 결과")
private String prcssrslt;
@Column(length = 4000)
@Comment("처리 결과 메세지")
private String prcssrsltcmnt;
@Override
public @NonNull Long getId() {
return seqno;
}
}
@@ -0,0 +1,7 @@
package com.eactive.eai.rms.data.entity.onl.layoutsync.history;
import com.eactive.eai.data.jpa.BaseRepository;
interface LayoutSyncHistoryRepository extends BaseRepository<LayoutSyncHistoryEntity, Long> {
}
@@ -0,0 +1,80 @@
package com.eactive.eai.rms.data.entity.onl.layoutsync.history;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.data.jpa.AbstractDataService;
import com.eactive.eai.rms.onl.manage.rule.layoutsync.history.ui.LayoutSyncHistoryUI;
import com.eactive.eai.rms.onl.manage.rule.layoutsync.history.ui.LayoutSyncHistoryUISearch;
import com.querydsl.jpa.impl.JPAQuery;
@Service
@Transactional
public class LayoutSyncHistoryService extends AbstractDataService<LayoutSyncHistoryEntity, Long, LayoutSyncHistoryRepository> {
private static final DateTimeFormatter FMT_YYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter FMT_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public Page<LayoutSyncHistoryUI> selectList(Pageable pageable, LayoutSyncHistoryUISearch search) {
QLayoutSyncHistoryEntity q = QLayoutSyncHistoryEntity.layoutSyncHistoryEntity;
JPAQuery<?> query = getJPAQueryFactory().from(q);
if (StringUtils.isNotBlank(search.getSearchLoutName())) {
query.where(q.loutname.containsIgnoreCase(search.getSearchLoutName()));
}
if (StringUtils.isNotBlank(search.getSearchStartDate())) {
query.where(q.recvamndhms.goe(parseYYYYMMDD(search.getSearchStartDate()).atStartOfDay()));
}
if (StringUtils.isNotBlank(search.getSearchEndDate())) {
query.where(q.recvamndhms.loe(parseYYYYMMDD(search.getSearchEndDate()).atTime(23, 59, 59)));
}
if (StringUtils.isNotBlank(search.getSearchPrcssRslt())) {
query.where(q.prcssrslt.eq(search.getSearchPrcssRslt()));
}
long totalCount = query.select(q.count()).fetchOne();
if (pageable.isPaged()) {
query.limit(pageable.getPageSize()).offset(pageable.getOffset());
}
List<LayoutSyncHistoryEntity> entities = query.select(q)
.orderBy(q.recvamndhms.desc(), q.seqno.desc())
.fetch();
List<LayoutSyncHistoryUI> rows = entities.stream().map(this::convertToUI).collect(Collectors.toList());
return new PageImpl<>(rows, pageable, totalCount);
}
public LayoutSyncHistoryUI selectDetail(Long seqno) {
return convertToUI(getById(seqno));
}
private static LocalDate parseYYYYMMDD(String yyyyMMdd) {
return LocalDate.parse(yyyyMMdd, FMT_YYYYMMDD);
}
private LayoutSyncHistoryUI convertToUI(LayoutSyncHistoryEntity e) {
LayoutSyncHistoryUI ui = new LayoutSyncHistoryUI();
ui.setSeqno(e.getSeqno());
ui.setServiceName(e.getServicename());
ui.setCommand(e.getCommand());
ui.setLoutName(e.getLoutname());
ui.setRecvAmndHms(e.getRecvamndhms() == null ? "" : e.getRecvamndhms().format(FMT_DATETIME));
ui.setPrcssRslt(e.getPrcssrslt());
ui.setPrcssRsltCmnt(e.getPrcssrsltcmnt());
ui.setRecvData(e.getRecvdata());
return ui;
}
}
@@ -102,9 +102,15 @@ public class EAILogRepositoryImpl implements EAILogRepository {
}
if (StringUtils.isNotBlank(eaiLogSearch.getSearchGuid())) {
query
.where(qeaiLog.pguid.eq(eaiLogSearch.getSearchGuid()))
.where(qeaiLog.id.logprcssserno.eq(eaiLogSearch.getSearchLogPrcssSerno()));
query.where(qeaiLog.pguid.eq(eaiLogSearch.getSearchGuid()));
// 처리단계(logprcssserno)를 명시적으로 선택한 경우에만 추가로 좁힌다.
// 하나의 GUID는 처리단계(100/200/300/400)별로 여러 로그 행에 걸쳐 존재하므로,
// 선택하지 않았으면 해당 GUID의 모든 단계를 조회한다.
if (StringUtils.isNotBlank(eaiLogSearch.getSearchLogPrcssSerno())) {
query.where(qeaiLog.id.logprcssserno.eq(eaiLogSearch.getSearchLogPrcssSerno()));
}
return;
}
@@ -13,6 +13,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.data.RollingTable;
import com.eactive.eai.data.entity.onl.logger.EAILog;
import com.eactive.eai.data.entity.onl.logger.EAILogId;
@@ -38,19 +39,36 @@ public class EAILogServiceImpl implements EAILogService {
@Override
public Page<EAILogDto> findAll(Pageable pageable, EAILogSearch eaiLogSearch) {
long time = CommonUtil.getCalendar(eaiLogSearch.getSearchStartTime()).getTimeInMillis();
long time = CommonUtil.getCalendar(resolveTableDate(eaiLogSearch)).getTimeInMillis();
EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time);
Page<EAILogDto> eaiLogDtos = eaiLogRepository.findAll(eaiLog.getClass(), pageable, eaiLogSearch);
return eaiLogDtos;
}
public Map<String, Long> getTransactionCounts(EAILogSearch eaiLogSearch) {
long time = CommonUtil.getCalendar(eaiLogSearch.getSearchStartTime()).getTimeInMillis();
long time = CommonUtil.getCalendar(resolveTableDate(eaiLogSearch)).getTimeInMillis();
EAILog eaiLog = (EAILog) applicationContext.getBean(RollingTable.class, EAILog.class, time);
Map<String, Long> detailedCounts = eaiLogRepository.countDetailedTransactions(eaiLog.getClass(), eaiLogSearch);
return detailedCounts;
}
// GUID로 조회하는 경우, 대상 롤링 테이블을 검색시간이 아닌 GUID 자체에 포함된 시각으로 특정한다.
// GUID 포맷/오프셋은 UUIDGenerator.GUID_DATE_POSITION 기준.
private String resolveTableDate(EAILogSearch eaiLogSearch) {
String guid = eaiLogSearch.getSearchGuid();
if (StringUtils.isNotBlank(guid)) {
int start = UUIDGenerator.GUID_DATE_POSITION;
int end = start + 14; // yyyyMMddHHmmss
if (guid.length() >= end) {
return guid.substring(start, end);
}
}
return eaiLogSearch.getSearchStartTime();
}
@Override
public EAILogDto getById(EAILogId eaiLogId) {
long time = CommonUtil.getCalendar(eaiLogId.getMsgdpstyms()).getTimeInMillis();
@@ -22,6 +22,7 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository;
import com.eactive.apim.portal.common.entity.Auditable;
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident;
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi;
@@ -48,6 +49,12 @@ import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiNameRow;
* 검수·게시해야 개발자포탈에 노출되며, {@code PUBLISH}면 탐지 즉시 노출된다.
* {@code OFF} 여도 이미 열려있는 장애의 복구 처리(ERROR_END/DELAY_END)는 계속 동작한다.</p>
*
* <p>개발자포탈 미게시 인터페이스 포함 여부는 {@code djb.apistatus.auto-incident-include-unpublished}
* (기본 {@code false}) 로 정한다. 기본값이면 게시 API 만 이슈에 담고,
* 게시 API 가 하나도 없는 탐지는 기록하지 않는다. {@code true}면 미게시 인터페이스도
* 포함해 건수로 집계된다. 복구 처리는 이 설정과 무관하게
* 이미 이슈에 담긴 API 를 대상으로 동작한다.</p>
*
* <p>점검(CONTROL_*) 은 관리자가 공지사항으로 등록한 점검 일정이 원천이다
* ({@code ApiStatusRepository.findApiStatusEvents} 가 INCIDENT 기간을 읽어 CTRL_YN 을 판정).
* 따라서 CONTROL 이벤트를 다시 기록하면 순환이 되므로 기록하지 않는다.</p>
@@ -58,8 +65,8 @@ public class ApiStatusDetectionService {
private static final Logger log = LoggerFactory.getLogger(ApiStatusDetectionService.class);
public static final String EVENT_CONTROL_START = "CONTROL_START";
public static final String EVENT_CONTROL_END = "CONTROL_END";
public static final String EVENT_CHECK_START = "CHECK_START";
public static final String EVENT_CHECK_END = "CHECK_END";
public static final String EVENT_ERROR_START = "ERROR_START";
public static final String EVENT_ERROR_END = "ERROR_END";
public static final String EVENT_DELAY_START = "DELAY_START";
@@ -95,6 +102,12 @@ public class ApiStatusDetectionService {
/** 오탐이 그대로 고객에게 노출되지 않도록 기본은 검수(DRAFT) */
private static final String DEFAULT_AUTO_INCIDENT_MODE = MODE_DRAFT;
/** 자동 탐지 이슈에 개발자포탈 미게시 인터페이스도 포함할지 여부 - true / false */
public static final String KEY_INCLUDE_UNPUBLISHED = "djb.apistatus.auto-incident-include-unpublished";
/** 포탈 게시 API 장애만 고객 공지 대상이 되도록 기본은 제외(false) */
private static final String DEFAULT_INCLUDE_UNPUBLISHED = "false";
@Autowired
private DjbApistatusIncidentRepository incidentRepository;
@@ -110,6 +123,10 @@ public class ApiStatusDetectionService {
@Autowired
private ApiStatusRepository apiStatusRepository;
/** EMSAPP(PTL_API_SPEC_INFO) 조회용. AGWAPP 커넥션으로 도는 apiStatusRepository 로는 못 읽는다 */
@Autowired
private ApiSpecInfoRepository apiSpecInfoRepository;
@Autowired
private PortalPropertyService portalPropertyService;
@@ -119,8 +136,8 @@ public class ApiStatusDetectionService {
/**
* 이벤트 감지
* @param event "CONTROL_START" : 점검시작
"CONTROL_END" : 점검종료
* @param event "CHECK_START" : 점검시작
"CHECK_END" : 점검종료
"ERROR_START" : 장애시작
"ERROR_END" : 장애종료
"DELAY_START" : 지연시작
@@ -154,8 +171,8 @@ public class ApiStatusDetectionService {
case EVENT_DELAY_END:
markRecovered(event, targets);
break;
case EVENT_CONTROL_START:
case EVENT_CONTROL_END:
case EVENT_CHECK_START:
case EVENT_CHECK_END:
// 점검은 관리자 등록 공지가 원천 - 재기록하지 않음
log.debug("점검 이벤트는 장애 데이터로 기록하지 않음: {} {}", event, targets);
break;
@@ -203,6 +220,19 @@ public class ApiStatusDetectionService {
}
boolean autoPublish = MODE_PUBLISH.equals(mode);
// 미게시 인터페이스 제외 설정(false)이면 게시 API 만 남기고, 하나도 없으면 기록하지 않는다
if (!resolveIncludeUnpublished()) {
List<String> published = filterPublished(apiIds);
if (published.isEmpty()) {
log.info("게시 API 없음 - 미게시 인터페이스 제외 설정으로 기록하지 않음: {} {}", event, apiIds);
return;
}
if (published.size() < apiIds.size()) {
log.info("미게시 인터페이스 제외: {} → {}", apiIds, published);
}
apiIds = published;
}
LocalDateTime now = LocalDateTime.now();
String interfaceId = makeInterfaceId(event, apiIds, now);
@@ -309,6 +339,27 @@ public class ApiStatusDetectionService {
}
}
/**
* 자동 탐지 이슈에 개발자포탈 미게시 인터페이스도 포함할지 여부.
*
* <p>PTL_PROPERTY({@code Portal} / {@code djb.apistatus.auto-incident-include-unpublished}) 를 읽는다.
* 최초 접근 시 기본값으로 row 가 자동 생성된다. 기본 {@code false} - 게시 API 만 이슈에 담는다.
* 명시적으로 {@code true}/{@code Y} 를 설정한 경우에만 미게시 인터페이스를 포함하며,
* 알 수 없는 값·조회 실패는 기본값 쪽(false)으로 떨어뜨린다.</p>
*/
private boolean resolveIncludeUnpublished() {
try {
String value = portalPropertyService.getOrCreateProperty(
PROPERTY_GROUP, KEY_INCLUDE_UNPUBLISHED, DEFAULT_INCLUDE_UNPUBLISHED,
"API 상태 자동 탐지 이슈에 포탈 미게시 인터페이스 포함 여부(true/false)");
String normalized = StringUtils.trimToEmpty(value);
return "true".equalsIgnoreCase(normalized) || "Y".equalsIgnoreCase(normalized);
} catch (Exception e) {
log.warn("미게시 인터페이스 포함 여부 조회 실패 - 기본값 {} 적용", DEFAULT_INCLUDE_UNPUBLISHED, e);
return false;
}
}
private void mergeIntoOpenIncident(DjbApistatusIncident incident, String event, IncidentKind kind,
List<String> apiIds, LocalDateTime now, boolean autoPublish) {
Long incidentId = incident.getIncidentId();
@@ -534,14 +585,47 @@ public class ApiStatusDetectionService {
}
}
/** 게시 인터페이스 판정. 조회에 실패하면 전부 비게시로 보아 ID·명칭 노출을 막는다. */
/**
* 게시 인터페이스 ID 집합 조회. 실패 처리는 호출부 정책에 맡긴다.
*
* <p>판정 조건 두 개가 서로 다른 스키마에 있어 조회를 나눈다 - API 그룹 편성은 AGWAPP,
* 스펙 존재는 EMSAPP(PTL_API_SPEC_INFO). 한 쿼리로 조인하면 tenant=APIGW 커넥션이
* EMSAPP 를 못 봐서 ORA-00942 가 난다.</p>
*/
private Set<String> lookupPublishedApiIds(List<String> apiIds) {
List<String> groupedIds = apiStatusRepository.findDisplayedGroupApiIds(apiIds);
return groupedIds.isEmpty()
? Collections.emptySet()
: new HashSet<>(apiSpecInfoRepository.findApiIdsIn(groupedIds));
}
/**
* 게시 API 만 남긴다 (미게시 인터페이스 제외 설정용). 판정에 실패하면 전체를 유지한다 -
* 제외 판단이 안 되는 상황에서 진짜 장애 기록까지 버리는 쪽이 더 위험하고,
* 명칭 노출은 표기 단계({@link #splitPublished})가 따로 막는다.
*/
private List<String> filterPublished(List<String> apiIds) {
try {
Set<String> publishedIds = lookupPublishedApiIds(apiIds);
return apiIds.stream()
.filter(publishedIds::contains)
.collect(Collectors.toList());
} catch (Exception e) {
log.warn("게시 API 판정 실패 - 미게시 인터페이스 제외 없이 전체 포함", e);
return apiIds;
}
}
/**
* 게시 인터페이스 판정. 조회에 실패하면 전부 비게시로 보아 ID·명칭 노출을 막는다.
*/
private ApiSplit splitPublished(List<String> apiIds) {
if (apiIds == null || apiIds.isEmpty()) {
return new ApiSplit(Collections.emptyList(), 0);
}
Set<String> publishedIds;
try {
publishedIds = new HashSet<>(apiStatusRepository.findPublishedApiIds(apiIds));
publishedIds = lookupPublishedApiIds(apiIds);
} catch (Exception e) {
// 실패 시 노출하는 쪽으로 기울면 내부 인터페이스가 고객 화면에 새어 나간다
log.warn("게시 API 판정 실패 - 전부 GW 인터페이스로 묶어 표기", e);
@@ -17,8 +17,8 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
/**
* API 상태 판단. 정상(N), 점검(C), 지연(D), 장애(E)
*
* 1. 정상/지연/장애 -> 점검중 : CONTROL_START
* 2. 점검 -> 점검X : CONTROL_END
* 1. 정상/지연/장애 -> 점검중 : CHECK_START
* 2. 점검 -> 점검X : CHECK_END
* 3. 정상/지연 -> 장애 : ERROR_START
* 4. 장애 -> 장애X : ERROR_END
* 5. 정상 -> 지연 : DELAY_START
@@ -34,8 +34,8 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
+ " FROM ("
+ " SELECT EAISVCNAME"
+ " , STATUS_CODE AS PREV_STATUS_CODE "
+ " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'"
+ " WHEN STATUS_CODE = 'C' AND CTRL_YN = 'N' THEN 'CONTROL_END'"
+ " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CHECK_START'"
+ " WHEN STATUS_CODE = 'C' AND CTRL_YN = 'N' THEN 'CHECK_END'"
+ " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'"
+ " WHEN STATUS_CODE = 'E' AND ERR_YN = 'N' THEN 'ERROR_END'"
+ " WHEN STATUS_CODE = 'N' AND DELAY_YN = 'Y' THEN 'DELAY_START'"
@@ -45,7 +45,7 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
+ " SELECT A.EAISVCNAME"
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
// 점검(KIND='MAINTENANCE')만 CTRL_YN 대상이다. KIND 를 안 걸면 관리자가 시작~종료 시각을
// 넣어 등록한 장애 공지까지 '점검중'으로 오판해 CONTROL_START 가 발생한다.
// 넣어 등록한 장애 공지까지 '점검중'으로 오판해 CHECK_START 가 발생한다.
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
+ " AND Y.KIND = 'MAINTENANCE'"
@@ -147,22 +147,25 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
List<ApiNameRow> findApiNames(@Param("apiIds") Collection<String> apiIds);
/**
* 개발자포탈에 게시되는 인터페이스(= API) 만 추린다.
* 노출 중인 API 그룹에 편성된 인터페이스 ID.
*
* <p>GW 인터페이스는 전부 탐지 대상이지만 개발자포탈에 노출되는 것은 그 일부다.
* 자동 초안(제목·공지 본문·타임라인)에 게시되지 않은 인터페이스 ID 가 그대로 박히면
* 고객 화면에 내부 인터페이스가 드러나므로, 여기서 걸러 나머지는 건수로만 표기한다.</p>
*
* <p>판정 기준은 포털 "오픈 API" 목록과 같은 두 조건이다 - 노출 중인 API 그룹에 편성돼 있고
* PTL_API_SPEC_INFO 에 스펙이 있을 것. 역할·소속에 따른 사용자별 공개 범위는 admin 에
* 사용자 컨텍스트가 없어 적용하지 않는다(= 포털 화면이 한 번 더 좁힌다).</p>
* <p>게시 판정은 포털 "오픈 API" 목록과 같은 두 조건이다 - 노출 중인 API 그룹에 편성돼 있고
* PTL_API_SPEC_INFO 에 스펙이 있을 것. 이 쿼리는 앞 조건(AGWAPP)만 본다. 뒤 조건은
* EMSAPP 스키마라 {@code ApiSpecInfoRepository.findApiIdsIn} 으로 따로 조회해 교집합을 잡는다
* - 이 리포지토리는 tenant=APIGW(AGWAPP) 커넥션으로 도는 탓에 한 쿼리로 조인하면 ORA-00942 다.</p>
*
* <p>역할·소속에 따른 사용자별 공개 범위는 admin 에 사용자 컨텍스트가 없어 적용하지 않는다
* (= 포털 화면이 한 번 더 좁힌다).</p>
*/
@Query(nativeQuery = true, value =
" SELECT DISTINCT GA.API_ID"
+ " FROM API_GROUP_API GA"
+ " JOIN API_GROUP G ON G.ID = GA.API_GROUP_ID"
+ " WHERE GA.API_ID IN (:apiIds)"
+ " AND G.DISPLAY_YN = '1'"
+ " AND EXISTS (SELECT 1 FROM EMSAPP.PTL_API_SPEC_INFO S WHERE S.API_ID = GA.API_ID)")
List<String> findPublishedApiIds(@Param("apiIds") Collection<String> apiIds);
+ " AND G.DISPLAY_YN = '1'")
List<String> findDisplayedGroupApiIds(@Param("apiIds") Collection<String> apiIds);
}
@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.data.entity.man.role.Role;
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiErrorRateRow;
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus;
@@ -36,6 +37,9 @@ public class ApiStatusService {
@Autowired
private ApiStatusDetectionService apiStatusDectionService;
@Autowired
private MonitoringContext monitoringContext;
public void updateApiStatus(HashMap<String, String> param) {
@@ -86,11 +90,16 @@ public class ApiStatusService {
String event = entry.getKey();
List<String> apiIds = entry.getValue();
HashMap<String, Object> params = getSwingChatMessage(event, apiIds);
ums.send("api-monitor", MessageCode.API_STATUS_CHANGED, params);
//내부직원 스윙챗 발송
if (monitoringContext.getBooleanProperty(MonitoringContext.DJB_UMS_MESSENGER_APIMONITOR_ENABLED, true)) {
HashMap<String, Object> params = getSwingChatMessage(event, apiIds);
ums.send("api-monitor", MessageCode.API_STATUS_CHANGED, params);
}
//제휴사 웹훅 발송
ums.sendWebhook(event, apiIds);
if (monitoringContext.getBooleanProperty(MonitoringContext.RMS_WEBHOOK_ENABLED, true)) {
ums.sendWebhook(event, apiIds);
}
//개발자포탈 API상태모니터링 화면을 위한 처리
apiStatusDectionService.detect(event, apiIds);
@@ -179,8 +188,8 @@ public class ApiStatusService {
private String resolveStatusCode(String event) {
switch (event) {
case "CONTROL_START": return "C"; //점검
case "CONTROL_END": return "N"; //정상
case "CHECK_START": return "C"; //점검
case "CHECK_END": return "N"; //정상
case "ERROR_START": return "E"; //장애
case "ERROR_END": return "N"; //정상
case "DELAY_START": return "D"; //지연
@@ -195,10 +204,10 @@ public class ApiStatusService {
private HashMap<String, Object> getSwingChatMessage(String event, List<String> apiIds) {
String message = "";
switch (event) {
case "CONTROL_START":
case "CHECK_START":
message = "API 서비스 점검이 시작되었습니다"; //점검
break;
case "CONTROL_END":
case "CHECK_END":
message = "API 서비스 점검이 완료되었습니다"; //정상
break;
case "ERROR_START":
@@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
@@ -23,6 +24,9 @@ public class InflowTokenService {
@Autowired
private InflowTokenRepository inflowTokenRepository;
@Autowired
private MonitoringContext monitoringContext;
@Autowired
private UmsManager ums;
@@ -32,9 +36,12 @@ public class InflowTokenService {
for (InflowTokenInsufficient info : rows) {
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
String message = "유량제어 토큰 획득 실패가 발생하였습니다 \n" + info.getEaisvcname();
HashMap<String,Object> params = new HashMap<String,Object>();
params.put("message", message);
ums.send("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, params);
if (monitoringContext.getBooleanProperty(MonitoringContext.DJB_UMS_MESSENGER_APIMONITOR_ENABLED, true)) {
HashMap<String,Object> params = new HashMap<String,Object>();
params.put("message", message);
ums.send("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, params);
}
}
}
}
@@ -6,7 +6,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
@@ -44,8 +43,12 @@ import com.eactive.eai.rms.onl.common.util.DateUtil;
* </ul>
*
* </pre>
*
* <p>동시 실행 허용: DB 커넥션풀 경합/락 대기로 특정 실행이 멈추더라도(트랜잭션 매니저가
* 다른 여러 퍼시스턴스 경로가 얽혀 있어 발생 가능) 다음 fire까지 막히지 않도록
* {@code @DisallowConcurrentExecution}을 두지 않는다. 상태 판정/알림 로직은
* interfaceId 시간창 기반 중복 제거가 있어 겹쳐 실행돼도 안전하다.</p>
*/
@DisallowConcurrentExecution
public class ApiStatusMonitorJob implements Job {
private static final Logger log = LoggerFactory.getLogger(ApiStatusMonitorJob.class);
@@ -3,12 +3,12 @@ package com.eactive.eai.rms.ext.djb.statistics;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@@ -35,9 +35,30 @@ public class ApiUseStatsController {
}
@PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<ApiStatsUI>> selectList(ApiStatsSearch search, Pageable pageable) {
Page<ApiStatsUI> page = service.selectList(search, pageable);
return ResponseEntity.ok(new GridResponse<>(page));
public ResponseEntity<GridResponse<ApiStatsUI>> selectList(ApiStatsSearch search) {
List<ApiStatsUI> list = service.selectList(search);
GridResponse<ApiStatsUI> response = new GridResponse<>(list);
ApiStatsUI total = service.selectTotal(search);
if (total != null) {
response.setUserdata(toFooterData(total));
}
return ResponseEntity.ok(response);
}
private Map<String, Object> toFooterData(ApiStatsUI total) {
Map<String, Object> footer = new LinkedHashMap<>();
footer.put("orgName", "합계");
footer.put("totalCnt", total.getTotalCnt());
footer.put("successCnt", total.getSuccessCnt());
footer.put("successRate", total.getSuccessRate());
footer.put("failRate", total.getFailRate());
footer.put("timeoutCnt", total.getTimeoutCnt());
footer.put("systemErrCnt", total.getSystemErrCnt());
footer.put("avgRespTime", total.getAvgRespTime());
footer.put("minRespTime", total.getMinRespTime());
footer.put("maxRespTime", total.getMaxRespTime());
return footer;
}
@PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=EXCEL_EXPORT")
@@ -57,6 +78,12 @@ public class ApiUseStatsController {
return;
}
ApiStatsUI total = service.selectTotal(search);
if (total != null) {
total.setOrgName("합계");
uiList.add(total);
}
String fileName = generateFileName(search);
log.info("Generating Excel file: {}", fileName);
@@ -10,9 +10,6 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -33,23 +30,6 @@ public class ApiUseStatsService
@PersistenceContext
private EntityManager entityManager;
@SuppressWarnings("unchecked")
public Page<ApiStatsUI> selectList(ApiStatsSearch search, Pageable pageable) {
Query dataQuery = getDataQuery(search);
dataQuery.setFirstResult((int) pageable.getOffset());
dataQuery.setMaxResults(pageable.getPageSize());
List<Object[]> rows = dataQuery.getResultList();
List<ApiStatsUI> results = rows.stream()
.map(this::toVO)
.collect(Collectors.toList());
long records = rows.size() > 0 ? StringUtils.toLong(rows.get(0)[0]) : 0;
return new PageImpl<>(results, pageable, records);
}
@SuppressWarnings("unchecked")
public List<ApiStatsUI> selectList(ApiStatsSearch search) {
Query dataQuery = getDataQuery(search);
@@ -60,14 +40,64 @@ public class ApiUseStatsService
}
/**
* 하단 합계(Total)행 조회 (grid footer 용, 그룹핑 없이 전체 집계)
*/
@SuppressWarnings("unchecked")
public ApiStatsUI selectTotal(ApiStatsSearch search) {
Query totalQuery = getTotalQuery(search);
List<Object[]> rows = totalQuery.getResultList();
return rows.isEmpty() ? null : toVO(rows.get(0));
}
private Query getDataQuery(ApiStatsSearch search) {
StringBuilder where = buildNativeWhere(search);
String dataSql = "";
if ("ORG".equals(search.getSearchType())) {
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
+ ", NVL(B.ORGNAME, D.ORG_NAME) AS ORGNAME"
+ ", SUM(A.TOTAL_CNT)"
dataSql = "SELECT NVL(B.ORGNAME, D.ORG_NAME) AS ORGNAME";
dataSql += getCommonQuery(search);
dataSql += " GROUP BY NVL(B.ORGNAME, D.ORG_NAME)";
dataSql += " ORDER BY ORGNAME";
}
else if ("API".equals(search.getSearchType())) {
dataSql = "SELECT A.API_NAME ";
dataSql += getCommonQuery(search);
dataSql += " GROUP BY A.API_NAME";
dataSql += " ORDER BY API_NAME";
}
else if ("DATE".equals(search.getSearchType())) {
dataSql = "SELECT TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME ";
dataSql += getCommonQuery(search);
dataSql += " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')";
dataSql += " ORDER BY STAT_TIME";
}
Query dataQuery = entityManager.createNativeQuery(dataSql);
bindParams(dataQuery, search);
return dataQuery;
}
private Query getTotalQuery(ApiStatsSearch search) {
StringBuilder where = buildNativeWhere(search);
String totalSql;
totalSql = "SELECT '합계' AS ORGNAME ";
totalSql += getCommonQuery(search);
Query totalQuery = entityManager.createNativeQuery(totalSql);
bindParams(totalQuery, search);
return totalQuery;
}
private String getCommonQuery(ApiStatsSearch search) {
String dataSql = ", SUM(A.TOTAL_CNT)"
+ ", SUM(A.SUCCESS_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
@@ -87,7 +117,7 @@ public class ApiUseStatsService
+ " , A.SYSTEM_ERR_CNT"
+ " , A.MIN_RESP_TIME"
+ " , A.MAX_RESP_TIME"
+ " , CASE WHEN API_NAME LIKE '%1' THEN SUBSTR(INBOUND_ADAPTER,2,3) ELSE SUBSTR(OUTBOUND_ADAPTER,2,3) END AS ORG_CODE"
+ " , CASE WHEN API_NAME LIKE '%1' THEN SUBSTR(OUTBOUND_ADAPTER,2,3) ELSE SUBSTR(INBOUND_ADAPTER,2,3) END AS ORG_CODE"
+ " FROM API_STATS_DAY A "
+ " WHERE A.STAT_TIME >= :searchStartDate "
+ " AND A.STAT_TIME <= :searchEndDate ";
@@ -106,61 +136,21 @@ public class ApiUseStatsService
dataSql += " AND UPPER(NVL(B.ORGNAME, D.ORG_NAME)) LIKE :searchOrgName ";
}
dataSql += " GROUP BY NVL(B.ORGNAME, D.ORG_NAME)"
+ " ORDER BY ORGNAME";
}
else if ("API".equals(search.getSearchType())) {
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
+ ", A.API_NAME"
+ ", SUM(A.TOTAL_CNT)"
+ ", SUM(A.SUCCESS_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", SUM(A.TIMEOUT_CNT)"
+ ", SUM(A.SYSTEM_ERR_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
+ ", MIN(A.MIN_RESP_TIME)"
+ ", MAX(A.MAX_RESP_TIME)"
+ " FROM API_STATS_DAY A "
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
+ where
+ " GROUP BY A.API_NAME"
+ " ORDER BY API_NAME";
}
else if ("DATE".equals(search.getSearchType())) {
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
+ ", SUM(A.TOTAL_CNT)"
+ ", SUM(A.SUCCESS_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
+ ", SUM(A.TIMEOUT_CNT)"
+ ", SUM(A.SYSTEM_ERR_CNT)"
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
+ ", MIN(A.MIN_RESP_TIME)"
+ ", MAX(A.MAX_RESP_TIME)"
+ " FROM API_STATS_DAY A "
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
+ where
+ " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')"
+ " ORDER BY STAT_TIME";
}
Query dataQuery = entityManager.createNativeQuery(dataSql);
return dataSql;
}
dataQuery.setParameter("searchStartDate", search.getParsedStartDate());
dataQuery.setParameter("searchEndDate", search.getParsedEndDate());
private void bindParams(Query query, ApiStatsSearch search) {
query.setParameter("searchStartDate", search.getParsedStartDate());
query.setParameter("searchEndDate", search.getParsedEndDate());
if (StringUtils.isNotBlank(search.getSearchApiName())) {
dataQuery.setParameter("searchApiName", "%" + search.getSearchApiName().toUpperCase() + "%");
query.setParameter("searchApiName", "%" + search.getSearchApiName().toUpperCase() + "%");
}
if (StringUtils.isNotBlank(search.getSearchOrgName())) {
dataQuery.setParameter("searchOrgName", "%" + search.getSearchOrgName().toUpperCase() + "%");
query.setParameter("searchOrgName", "%" + search.getSearchOrgName().toUpperCase() + "%");
}
return dataQuery;
}
@@ -169,16 +159,16 @@ public class ApiUseStatsService
*/
private ApiStatsUI toVO(Object[] row) {
ApiStatsUI vo = new ApiStatsUI();
vo.setOrgName(StringUtils.toString(row[1]));
vo.setTotalCnt(StringUtils.toLong(row[2]));
vo.setSuccessCnt(StringUtils.toLong(row[3]));
vo.setSuccessRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP));
vo.setFailRate(StringUtils.toDecimal(row[5]).setScale(2, RoundingMode.HALF_UP));
vo.setTimeoutCnt(StringUtils.toLong(row[6]));
vo.setSystemErrCnt(StringUtils.toLong(row[7]));
vo.setAvgRespTime(StringUtils.toDecimal(row[8]));
vo.setMinRespTime(StringUtils.toDecimal(row[9]));
vo.setMaxRespTime(StringUtils.toDecimal(row[10]));
vo.setOrgName(StringUtils.toString(row[0]));
vo.setTotalCnt(StringUtils.toLong(row[1]));
vo.setSuccessCnt(StringUtils.toLong(row[2]));
vo.setSuccessRate(StringUtils.toDecimal(row[3]).setScale(2, RoundingMode.HALF_UP));
vo.setFailRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP));
vo.setTimeoutCnt(StringUtils.toLong(row[5]));
vo.setSystemErrCnt(StringUtils.toLong(row[6]));
vo.setAvgRespTime(StringUtils.toDecimal(row[7]));
vo.setMinRespTime(StringUtils.toDecimal(row[8]));
vo.setMaxRespTime(StringUtils.toDecimal(row[9]));
return vo;
}
@@ -3,6 +3,7 @@ package com.eactive.eai.rms.ext.djb.ums;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
@@ -43,7 +44,10 @@ public class UmsManager {
}
for (UserInfo user : users) {
String cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
String cleanedPhone = "";
if (StringUtils.isNotEmpty(user.getCphnno())) {
cleanedPhone = user.getCphnno().replaceAll("[^0-9]", "").trim();
}
MessageRecipient recipient = MessageRecipient.builder()
.userId(user.getUserid())
.username(user.getUsername())
@@ -52,6 +56,7 @@ public class UmsManager {
.messengerId(user.getUserid())
.build();
messageHandlerService.publishEvent(messageCode, recipient, params);
}
}
@@ -166,10 +166,16 @@ public class UmsService {
if ("MESSENGER".equals(messageRequest.getMessageType()) ) {
stdHeadVO.setChnl_tycd("FEP"); // 채널유형코드
stdHeadVO.setHmab_dvcd("2"); // 내외구분코드 1:내부 2:외부(타발)
stdHeadVO.setTx_brcd("132");
stdHeadVO.setTlrno("ET0001");
stdHeadVO.setInsl_brcd("132");
stdHeadVO.setBlng_brcd("132");
stdHeadVO.setMgmt_brcd("132");
// 스윙챗 알림 공용
SwingCommonVO swiComVO = new SwingCommonVO();
swiComVO.setClientld(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_id", ""));
swiComVO.setClientId(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_id", ""));
swiComVO.setClientSecret(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_secret", ""));
swiComVO.setCompanyCode("CB");
swiComVO.setRequestUniqueKey(this.genrateRandomStringNumber26());
@@ -186,6 +192,7 @@ public class UmsService {
receiver.setType("PSN");
receiver.setCompanyCode("CB");
receiver.setCode(messageRequest.getMessengerId());
receiver.setName(messageRequest.getUsername());
swiReDataVOList.add(receiver);
swiDataVO.setReceiverInfo(swiReDataVOList);
@@ -253,12 +260,9 @@ public class UmsService {
dataVO.setUms_evnt_id(propertyService.getPropertyValue("Monitoring", "djb.ums.email.ums_evnt_id", "")); // UMS이벤트ID(필수)
dataVO.setSnd_dman_dt(jsonData[0]);
dataVO.setSnd_dman_id(this.getSndDmanId(messageRequest.getMessageType()));
dataVO.setSnd_dman_sno("1");
dataVO.setDman_seqno(1); // 발송요청순번
dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목
dataVO.setCstno(propertyService.getPropertyValue("Monitoring", "djb.ums.email.cstno", "")); // 고객번호(필수)
dataVO.setSendr_emaddr(propertyService.getPropertyValue("Monitoring", "djb.ums.email.sendr_emaddr", "")); // 발신자 이메일
dataVO.setSendr_nm(propertyService.getPropertyValue("Monitoring", "djb.ums.email.sendr_nm", "")); // 발신자 이름
dataVO.setSndbk_emaddr(propertyService.getPropertyValue("Monitoring", "djb.ums.email.sndbk_emaddr", "")); // 리턴 이메일
dataVO.setRecvr_emaddr(messageRequest.getEmail()); // 수신자 이메일(필수)
dataVO.setRecvr_nm(messageRequest.getUsername()); // 수신자명(필수)
dataVO.setTmplt_mpng_info(messageRequest.getMessage()); // 탬플릿매핑정보
@@ -15,7 +15,7 @@ public class EmailDataVO {
private String snd_dman_id;
/** 발송요청순번 */
private String snd_dman_sno;
private int dman_seqno;
/** 메일제목 */
private String email_titl;
@@ -6,7 +6,7 @@ import lombok.Data;
public class SwingCommonVO {
/** 클라이업트 D */
private String clientld;
private String clientId;
/** 클라이언트 시크릿 */
private String clientSecret;
@@ -79,7 +79,7 @@ public class WebhookSendController {
}
// 2. 서명 검증
boolean isValid = webhookReceiveService.verifySignature(rawPayload, signature);
boolean isValid = webhookReceiveService.verifySignature(rawPayload, signature, "");
if (!isValid) {
log.warn("[Webhook] 수신 거부 - 서명 불일치 / eventType: {}", eventType);
return ResponseEntity
@@ -20,7 +20,6 @@ public class WebhookReceiveService {
private final ObjectMapper objectMapper;
private String secretKey = "HW8JtFpkPmQqsVmr0Rb81P4qDypaNIVbGf3ZNMMPfyerhOHshoKABLaMBo1HA4BldTxhw1NjYmVnoPu9IIV7cyRXjecL3b2UctyR7DnVaJausltZLLr8Qm4Bzs4wRmgf";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final long TIMESTAMP_LIMIT = 5 * 60 * 1000L; // 5분 (ms)
@@ -33,14 +32,14 @@ public class WebhookReceiveService {
* 수신된 서명과 payload 로 재계산한 서명을 비교
* 헤더값 형식 : "sha256={hex값}"
*/
public boolean verifySignature(String rawPayload, String receivedSignature) {
public boolean verifySignature(String rawPayload, String receivedSignature, String secretKey) {
try {
// "sha256=" 접두어 제거
String receivedHex = receivedSignature.startsWith("sha256=")
? receivedSignature.substring(7)
: receivedSignature;
String expectedHex = generateSignature(rawPayload);
String expectedHex = generateSignature(rawPayload, secretKey);
// 타이밍 공격 방지 : MessageDigest.isEqual 사용
return MessageDigest.isEqual(
@@ -85,10 +84,10 @@ public class WebhookReceiveService {
log.info("[Webhook] 이벤트 처리 시작 - eventType: {}", eventType);
switch (eventType) {
case "CONTROL_START":
case "CHECK_START":
handleControlStart(data);
break;
case "CONTROL_END":
case "CHECK_END":
handleControlEnd(data);
break;
case "ERROR_START":
@@ -107,11 +106,11 @@ public class WebhookReceiveService {
/* ------------------------------------------------------------------ */
private void handleControlStart(JsonNode data) {
log.info("[Webhook] CONTROL_START 처리 - data: {}", data);
log.info("[Webhook] CHECK_START 처리 - data: {}", data);
// TODO: 점검시작 처리 로직
}
private void handleControlEnd(JsonNode data) {
log.info("[Webhook] CONTROL_END 처리 - data: {}", data);
log.info("[Webhook] CHECK_END 처리 - data: {}", data);
// TODO: 점검종료 처리 로직
}
private void handleErrorStart(JsonNode data) {
@@ -124,7 +123,7 @@ public class WebhookReceiveService {
/* 공통 유틸 */
/* ------------------------------------------------------------------ */
private String generateSignature(String payload) throws Exception {
private String generateSignature(String payload, String secretKey) throws Exception {
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
@@ -90,7 +90,14 @@ public class WebhookService {
wr.setData(new ArrayList<String>());
return wr;
});
((List<String>) resultMap.get(key).getData()).add(t.get(api.id.apiId));
WebhookSendRequest wsr = resultMap.get(key);
if (wsr != null) {
List<String> list = (List<String>) wsr.getData();
if (list != null) {
list.add(t.get(api.id.apiId));
}
}
}
return new ArrayList<>(resultMap.values());
@@ -244,6 +244,12 @@ public class PortalApprovalManService extends BaseService {
appApprovalUI.setApprovalDetail(appRequest.getReason());
appApprovalUI.setClientName(appRequest.getClientName());
// 신청 사유는 상세 내용과 별개 항목으로도 내려준다(해지 신청 화면에서 "해지 사유"로 표기)
appApprovalUI.setRequestReason(appRequest.getReason());
// 승인 시 GW 클라이언트로 그대로 반영되는 항목이므로 승인자가 검토할 수 있게 함께 내려준다.
appApprovalUI.setIpWhitelist(appRequest.getIpWhitelist());
appApprovalUI.setCallbackUrl(appRequest.getCallbackUrl());
appApprovalUI.setAppDescription(appRequest.getAppDescription());
appApprovalUI.getApprovers().forEach(approver -> {
if (approver.getUser() == null) {
@@ -0,0 +1,84 @@
package com.eactive.eai.rms.onl.apim.approval.app;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apprequest.entity.GwAction;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
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.onl.apim.approval.credential.CredentialUI;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* APP 승인 시 GW 인증 클라이언트(APIGW 스키마 TSEAIAU01) 반영을 <b>승인 트랜잭션과 분리된 새 트랜잭션</b>에서
* 수행하고 커밋까지 끝낸다.
*
* <p>분리가 필요한 이유: GW 는 RELOAD_CLIENT 명령을 받으면 자기 커넥션으로 DB 를 다시 읽어 메모리에 적재한다
* ({@code OAuth2Manager#reloadClient} 는 캐시에서 제거 후 {@code getClientById} 결과가 있을 때만 다시 넣는다).
* 승인 트랜잭션 안에서 INSERT 한 직후 broadcast 하면 그 행이 아직 커밋되지 않아 GW 가 찾지 못하고,
* 신규(NEW) 클라이언트는 캐시에 올라가지 못한 채 리로드가 무의미해진다. 변경(MODIFY)도 커밋 전 옛 값이
* 다시 적재된다. 따라서 broadcast 는 반드시 이 메서드가 반환(=커밋)된 뒤에 수행해야 한다.
*
* <p>트랜잭션 매니저는 APIGW 스키마를 라우팅하는 멀티테넌트 EMF 쪽({@code transactionManager})을 사용한다.
* Hibernate 는 Session 을 열 때 테넌트를 한 번만 해석하므로, 새 트랜잭션을 여는 이 시점에
* {@link DataSourceContextHolder} 가 APIGW 로 설정돼 있어야 한다.
*/
@Service
@RequiredArgsConstructor
public class GwClientSyncService {
private static final Logger logger = LoggerFactory.getLogger(GwClientSyncService.class);
private final ClientManService clientManService;
private final CredentialUIMapper credentialUIMapper;
@Transactional(transactionManager = "transactionManager",
propagation = Propagation.REQUIRES_NEW,
rollbackFor = ApprovalDeployException.class)
public void apply(AppRequest appRequest, CredentialUI credentialUI) throws ApprovalDeployException {
// 새 Session 이 열리는 시점의 테넌트가 APIGW 여야 TSEAIAU01 에 접근할 수 있다.
DataSourceType type = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
DataSourceContextHolder.setDataSourceType(type);
AppRequestType action = appRequest.getType();
if (AppRequestType.NEW.equals(action)) {
clientManService.insert(credentialUIMapper.mapClient(credentialUI));
} else if (AppRequestType.MODIFY.equals(action)) {
clientManService.update(credentialUIMapper.mapClient(credentialUI));
} else if (AppRequestType.DELETE.equals(action)) {
applyGwActionForDelete(appRequest.getGwAction(), credentialUI.getClientid());
}
}
/**
* API 이용해지(DELETE) 승인 시 GW 클라이언트(TSEAIAU01) 처리.
* 승인 시점에 관리자가 선택해 PTL_APP_REQUEST.GW_ACTION 에 영속화된 값을 따른다.
* - DELETE: 행 완전 삭제 / - BLOCK 또는 null(미지정): 차단(appstatus='0')
* GW 에 행이 이미 없으면(선삭제·재처리) 목적이 달성된 상태이므로 통과시키고,
* 그 외 오류는 ApprovalDeployException 으로 던져 FAILED(재처리 가능) 상태를 만든다.
*/
private void applyGwActionForDelete(GwAction gwAction, String clientId) throws ApprovalDeployException {
try {
if (GwAction.DELETE.equals(gwAction)) {
clientManService.delete(clientId);
logger.info("GW 클라이언트 완전 삭제 - clientId: {}", clientId);
} else {
clientManService.blockClient(clientId);
logger.info("GW 클라이언트 차단(appstatus=0) - clientId: {}", clientId);
}
} catch (org.springframework.dao.EmptyResultDataAccessException | javax.persistence.EntityNotFoundException e) {
logger.warn("GW 클라이언트가 이미 존재하지 않아 처리 생략 (재처리 통과) - clientId: {}", clientId);
} catch (RuntimeException e) {
logger.error("GW 클라이언트 해지 처리 실패 - clientId: {}, gwAction: {}", clientId, gwAction, e);
throw new ApprovalDeployException("GW 클라이언트 해지 처리에 실패했습니다: " + e.getMessage());
}
}
}
@@ -2,7 +2,6 @@ package com.eactive.eai.rms.onl.apim.approval.app;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apprequest.entity.GwAction;
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
@@ -10,6 +9,7 @@ import com.eactive.apim.portal.approval.statemachine.listener.ApprovalListener;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageRecipient;
import com.eactive.apim.portal.template.service.MessageSendService;
import com.eactive.eai.agent.command.CommandResult;
import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceType;
@@ -18,9 +18,7 @@ import com.eactive.eai.rms.data.entity.onl.apim.apigroup.ApiGroupService;
import com.eactive.eai.rms.onl.apim.approval.RandomStringGenerator;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialManService;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
import java.util.Arrays;
import java.util.Collections;
@@ -55,9 +53,8 @@ public class PortalAppApprovalListener implements ApprovalListener {
private final ApiGroupService apiGroupService;
private final AppRequestRepository appRequestRepository;
private final CredentialManService credentialManService;
private final CredentialUIMapper credentialUIMapper;
private final ClientManService clientManService;
private final MessageSendService messageSendService;
private final GwClientSyncService gwClientSyncService;
@Override
@@ -127,47 +124,23 @@ public class PortalAppApprovalListener implements ApprovalListener {
private void syncTargetServer(AppRequest appRequest, CredentialUI credentialUI) throws ApprovalDeployException {
AppRequestType action = appRequest.getType();
DataSourceType type = DataSourceTypeManager.getDataSourceType("APIGW");
// TSEAIAU01/TSEAISY02 는 APIGW 스키마에 있으므로 라우팅을 먼저 설정한다.
// (GW 반영은 새 트랜잭션에서, 이어지는 broadcast 의 서버목록 조회도 이 테넌트로 수행된다)
DataSourceType type = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
DataSourceContextHolder.setDataSourceType(type);
if (action.equals(AppRequestType.NEW)) {
clientManService.insert(credentialUIMapper.mapClient(credentialUI));
} else if (action.equals(AppRequestType.MODIFY)) {
clientManService.update(credentialUIMapper.mapClient(credentialUI));
} else if (action.equals(AppRequestType.DELETE)) {
applyGwActionForDelete(appRequest.getGwAction(), credentialUI.getClientid());
}
// GW 반영은 별도 트랜잭션에서 커밋까지 끝낸다. GW 는 리로드 명령을 받고 DB 를 다시 읽으므로
// 커밋 전에 broadcast 하면 신규 클라이언트를 찾지 못해 리로드가 무의미해진다.
gwClientSyncService.apply(appRequest, credentialUI);
CommonCommand.builder()
// broadcast 는 서버별 전송 실패를 예외로 던지지 않고 결과 맵에 담아 정상 반환한다.
// 결과를 버리면 리로드 실패가 아무 흔적 없이 묻히므로 서버별 응답을 반드시 남긴다.
CommandResult reloadResult = CommonCommand.builder()
.name(CommonCommand.RELOAD_CLIENT_COMMAND)
.args(credentialUI.getClientid())
.build().broadcast(agentUtilService);
}
/**
* API 이용해지 승인 시 GW 클라이언트(TSEAIAU01) 처리.
* 승인 시점에 관리자가 선택해 PTL_APP_REQUEST.GW_ACTION 에 영속화된 값을 따른다.
* - DELETE: 행 완전 삭제 / - BLOCK 또는 null(미지정): 차단(appstatus='0')
* GW 에 행이 이미 없으면(선삭제·재처리) 목적이 달성된 상태이므로 통과시키고,
* 그 외 오류는 ApprovalDeployException 으로 던져 FAILED(재처리 가능) 상태를 만든다.
*/
private void applyGwActionForDelete(GwAction gwAction, String clientId) throws ApprovalDeployException {
try {
if (GwAction.DELETE.equals(gwAction)) {
clientManService.delete(clientId);
logger.info("GW 클라이언트 완전 삭제 - clientId: {}", clientId);
} else {
clientManService.blockClient(clientId);
logger.info("GW 클라이언트 차단(appstatus=0) - clientId: {}", clientId);
}
} catch (org.springframework.dao.EmptyResultDataAccessException | javax.persistence.EntityNotFoundException e) {
logger.warn("GW 클라이언트가 이미 존재하지 않아 처리 생략 (재처리 통과) - clientId: {}", clientId);
} catch (RuntimeException e) {
logger.error("GW 클라이언트 해지 처리 실패 - clientId: {}, gwAction: {}", clientId, gwAction, e);
throw new ApprovalDeployException("GW 클라이언트 해지 처리에 실패했습니다: " + e.getMessage());
}
logger.info("GW 클라이언트 리로드 broadcast - clientId: {}, 서버별 응답: {}",
credentialUI.getClientid(), reloadResult.getMessage(false));
}
private CredentialUI modifyClientUI(AppRequest appRequest) {
@@ -30,4 +30,12 @@ public class PortalAppApprovalUI extends PortalApprovalUI {
//DELETE 승인 시 선택된 GW 처리방식 (승인 완료 건 상세 표시용)
private GwAction gwAction;
//신청자가 입력한 사유(PTL_APP_REQUEST.REASON). 해지 신청은 사유가 필수라 별도 항목으로 노출한다.
private String requestReason;
//신청서(PTL_APP_REQUEST)에 담긴 Client 등록 정보. 승인 시 GW(TSEAIAU01)로 그대로 반영되므로 승인 화면에 노출한다.
private String ipWhitelist;
private String callbackUrl;
private String appDescription;
}
@@ -58,7 +58,11 @@ public class MessageRequestManService extends BaseService {
ui.setMessageType(e.getMessageType());
// 본문: 패턴 마스킹 + 색상강조 안전 HTML
ui.setMessageHtml(MessagePatternMaskingUtils.maskAndHighlight(e.getMessage()));
if (e.getMessageCode() == MessageCode.API_STATUS_CHANGED) {
ui.setMessageHtml(e.getMessage());
} else {
ui.setMessageHtml(MessagePatternMaskingUtils.maskAndHighlight(e.getMessage()));
}
// 수신자 마스킹 (email/phone 은 @Convert 로 이미 복호화된 평문)
ui.setUsername(MaskingUtils.maskName(e.getUsername()));
@@ -1,22 +1,48 @@
package com.eactive.eai.rms.onl.apim.portalmenu;
import com.eactive.apim.portal.common.internal.InternalApiTokenService;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* eapim-portal 메뉴 캐시 리로드 클라이언트 (admin → portal 내부 호출).
*
* <p>호출 URL 은 PTL_PROPERTY {@code Portal / portal.internal.menu-reload-url} 로 관리한다
* (기본 {@code http://127.0.0.1:39130/internal/menu/reload}).
* portal 측은 {@code menu.internal.allow-ips} 허용 IP 목록으로 요청을 검증한다.
* 오류는 결과 Map 으로 감싸 화면에 사유를 표시한다 — 예외를 전파하지 않는다.</p>
* 포탈이 다중 인스턴스로 기동되는 환경을 위해 <b>콤마/세미콜론/줄바꿈</b> 으로 여러 URL 을 나열할 수 있으며,
* 나열된 모든 서버로 병렬 호출한다. 각 호출의 connect/read 타임아웃은 {@value #TIMEOUT_MS}ms 이다.
* 오류는 결과 Map 으로 감싸 화면에 서버별 사유를 표시한다 — 예외를 전파하지 않는다.</p>
*
* <p>portal 측 검증은 두 겹이다: {@code menu.internal.allow-ips} 허용 IP 목록과
* {@link InternalApiTokenService} 공유 토큰 헤더. 토큰/헤더명은 PTL_PROPERTY 로 portal 과 공유하므로
* 이 클래스는 <b>읽기만</b> 한다(생성 주체는 portal). 값이 아직 없으면 헤더 없이 호출하게 되어 portal 이
* 401 로 거부하며, 이때는 portal 을 먼저 기동해 토큰을 생성해야 한다.</p>
*/
@Component
public class PortalMenuCacheClient {
@@ -26,50 +52,251 @@ public class PortalMenuCacheClient {
static final String PROP_GROUP = "Portal";
static final String PROP_RELOAD_URL = "portal.internal.menu-reload-url";
static final String DEFAULT_RELOAD_URL = "http://127.0.0.1:39130/internal/menu/reload";
static final String PROP_DESCRIPTION =
"eapim-portal 메뉴 캐시 리로드 내부 URL. 다중 서버는 콤마(,)/세미콜론(;)/줄바꿈으로 구분";
/** 서버 1대당 connect/read 타임아웃(ms) */
static final int TIMEOUT_MS = 3000;
/** 병렬 호출 전체 대기 여유분(ms) — 스레드 시작/응답 파싱 지연 흡수 */
private static final int OVERALL_SLACK_MS = 2000;
/** 동시 호출 스레드 상한 */
private static final int MAX_PARALLELISM = 10;
private final PortalPropertyService portalPropertyService;
private final InternalApiTokenService internalApiTokenService;
private final RestTemplate restTemplate;
@Autowired
public PortalMenuCacheClient(PortalPropertyService portalPropertyService, RestTemplate restTemplate) {
public PortalMenuCacheClient(PortalPropertyService portalPropertyService,
InternalApiTokenService internalApiTokenService) {
this(portalPropertyService, internalApiTokenService, createRestTemplateWithTimeout(TIMEOUT_MS));
}
/** 테스트용 — 타임아웃이 설정된 RestTemplate 을 직접 주입한다. */
PortalMenuCacheClient(PortalPropertyService portalPropertyService,
InternalApiTokenService internalApiTokenService,
RestTemplate restTemplate) {
this.portalPropertyService = portalPropertyService;
this.internalApiTokenService = internalApiTokenService;
this.restTemplate = restTemplate;
}
private static RestTemplate createRestTemplateWithTimeout(int timeoutMs) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(timeoutMs)
.setConnectTimeout(timeoutMs)
.setSocketTimeout(timeoutMs)
.build();
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(MAX_PARALLELISM * 2)
.setMaxConnPerRoute(MAX_PARALLELISM)
.setDefaultRequestConfig(requestConfig)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(factory);
return restTemplate;
}
/**
* @return {@code {success, message, itemCount?, reloadedAt?}}
* 설정된 모든 포탈 서버의 메뉴 캐시를 리로드한다.
*
* @return {@code {success, message, total, successCount, failCount, itemCount?, reloadedAt?,
* servers:[{url, success, message, itemCount?, reloadedAt?, elapsedMs}]}}
* — {@code success} 는 <b>모든</b> 서버가 성공한 경우에만 true
*/
public Map<String, Object> reload() {
Map<String, Object> result = new LinkedHashMap<>();
String url = resolveUrl();
try {
@SuppressWarnings("unchecked")
ResponseEntity<Map> response = restTemplate.postForEntity(url, null, Map.class);
Map<String, Object> body = response.getBody();
if (response.getStatusCode().is2xxSuccessful() && body != null && "OK".equals(body.get("result"))) {
result.put("success", true);
result.put("message", "포탈 메뉴 캐시 리로드 완료");
result.put("itemCount", body.get("itemCount"));
result.put("reloadedAt", body.get("reloadedAt"));
} else {
result.put("success", false);
result.put("message", "포탈 응답 오류: " + (body == null ? response.getStatusCode() : body));
List<String> urls = resolveUrls();
// 토큰/헤더명 조회는 리로드 1회당 1번만 (서버 수만큼 DB 를 때리지 않는다)
List<Map<String, Object>> servers = callAll(urls, buildRequest());
int successCount = 0;
for (Map<String, Object> server : servers) {
if (Boolean.TRUE.equals(server.get("success"))) {
successCount++;
}
}
int failCount = servers.size() - successCount;
Map<String, Object> result = new LinkedHashMap<>();
result.put("success", failCount == 0);
result.put("message", summarize(servers.size(), successCount, failCount));
result.put("total", servers.size());
result.put("successCount", successCount);
result.put("failCount", failCount);
result.put("servers", servers);
// 단일 서버 시절 응답과의 호환 — 첫 성공 서버의 값을 최상위에도 노출
for (Map<String, Object> server : servers) {
if (Boolean.TRUE.equals(server.get("success"))) {
result.put("itemCount", server.get("itemCount"));
result.put("reloadedAt", server.get("reloadedAt"));
break;
}
} catch (Exception e) {
logger.error("포탈 메뉴 캐시 리로드 실패 - url: " + url, e);
result.put("success", false);
result.put("message", "포탈 호출 실패(" + url + "): " + e.getMessage());
}
return result;
}
private String resolveUrl() {
private String summarize(int total, int successCount, int failCount) {
if (total == 0) {
return "리로드 대상 URL 이 설정되지 않았습니다 (" + PROP_GROUP + " / " + PROP_RELOAD_URL + ")";
}
if (failCount == 0) {
return "포탈 메뉴 캐시 리로드 완료 (" + successCount + "/" + total + " 서버)";
}
if (successCount == 0) {
return "포탈 메뉴 캐시 리로드 실패 (0/" + total + " 서버)";
}
return "포탈 메뉴 캐시 리로드 일부 실패 (" + successCount + "/" + total + " 서버 성공)";
}
/**
* 내부 API 인증 헤더를 실은 빈 본문 요청. 토큰이 아직 없으면 헤더 없이 나가고 포탈이 401 로 거부한다.
*/
private HttpEntity<Void> buildRequest() {
HttpHeaders headers = new HttpHeaders();
String token = internalApiTokenService.findToken();
if (token == null) {
logger.warn("포탈 내부 API 토큰 없음 - PTL_PROPERTY " + InternalApiTokenService.PROP_GROUP + "/"
+ InternalApiTokenService.PROP_TOKEN + " (포탈 최초 기동 시 자동 생성)");
} else {
headers.set(internalApiTokenService.findHeaderName(), token);
}
return new HttpEntity<Void>(headers);
}
/** 모든 URL 로 병렬 호출. 반환 순서는 {@code urls} 순서와 동일하다. */
private List<Map<String, Object>> callAll(List<String> urls, final HttpEntity<Void> request) {
if (urls.isEmpty()) {
return Collections.emptyList();
}
if (urls.size() == 1) {
return Collections.singletonList(callOne(urls.get(0), request));
}
List<Callable<Map<String, Object>>> tasks = new ArrayList<>(urls.size());
for (final String url : urls) {
tasks.add(new Callable<Map<String, Object>>() {
@Override
public Map<String, Object> call() {
return callOne(url, request);
}
});
}
ExecutorService executor = Executors.newFixedThreadPool(Math.min(urls.size(), MAX_PARALLELISM));
try {
return portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_RELOAD_URL,
DEFAULT_RELOAD_URL, "eapim-portal 메뉴 캐시 리로드 내부 URL");
} catch (Exception e) {
logger.warn("리로드 URL 조회 실패 - 기본값 사용: " + DEFAULT_RELOAD_URL, e);
return DEFAULT_RELOAD_URL;
List<Future<Map<String, Object>>> futures =
executor.invokeAll(tasks, TIMEOUT_MS + OVERALL_SLACK_MS, TimeUnit.MILLISECONDS);
List<Map<String, Object>> results = new ArrayList<>(urls.size());
for (int i = 0; i < futures.size(); i++) {
String url = urls.get(i);
Future<Map<String, Object>> future = futures.get(i);
try {
results.add(future.get());
} catch (CancellationException e) {
logger.warn("포탈 메뉴 캐시 리로드 타임아웃 - url: " + url);
results.add(failure(url, "응답 시간 초과(" + TIMEOUT_MS + "ms)", TIMEOUT_MS));
} catch (ExecutionException e) {
logger.error("포탈 메뉴 캐시 리로드 실패 - url: " + url, e.getCause());
results.add(failure(url, message(e.getCause()), 0L));
}
}
return results;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("포탈 메뉴 캐시 리로드 중단됨", e);
List<Map<String, Object>> results = new ArrayList<>(urls.size());
for (String url : urls) {
results.add(failure(url, "호출이 중단되었습니다", 0L));
}
return results;
} finally {
executor.shutdownNow();
}
}
/** 서버 1대 호출 — 예외를 전파하지 않고 실패 Map 으로 감싼다. */
private Map<String, Object> callOne(String url, HttpEntity<Void> request) {
long startedAt = System.currentTimeMillis();
try {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.POST, request, Map.class);
long elapsed = System.currentTimeMillis() - startedAt;
@SuppressWarnings("unchecked")
Map<String, Object> body = response.getBody();
if (response.getStatusCode().is2xxSuccessful() && body != null && "OK".equals(body.get("result"))) {
Map<String, Object> server = new LinkedHashMap<>();
server.put("url", url);
server.put("success", true);
server.put("message", "리로드 완료");
server.put("itemCount", body.get("itemCount"));
server.put("reloadedAt", body.get("reloadedAt"));
server.put("elapsedMs", elapsed);
return server;
}
logger.warn("포탈 메뉴 캐시 리로드 응답 오류 - url: " + url + ", status: " + response.getStatusCode()
+ ", body: " + body);
return failure(url, "포탈 응답 오류: " + (body == null ? response.getStatusCode() : body), elapsed);
} catch (Exception e) {
long elapsed = System.currentTimeMillis() - startedAt;
logger.error("포탈 메뉴 캐시 리로드 실패 - url: " + url, e);
return failure(url, message(e), elapsed);
}
}
private Map<String, Object> failure(String url, String message, long elapsedMs) {
Map<String, Object> server = new LinkedHashMap<>();
server.put("url", url);
server.put("success", false);
server.put("message", message);
server.put("elapsedMs", elapsedMs);
return server;
}
private String message(Throwable t) {
if (t == null) {
return "알 수 없는 오류";
}
String msg = t.getMessage();
return (msg == null || msg.trim().isEmpty()) ? t.getClass().getSimpleName() : msg;
}
private List<String> resolveUrls() {
String raw;
try {
raw = portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_RELOAD_URL,
DEFAULT_RELOAD_URL, PROP_DESCRIPTION);
} catch (Exception e) {
logger.warn("리로드 URL 조회 실패 - 기본값 사용: " + DEFAULT_RELOAD_URL, e);
raw = DEFAULT_RELOAD_URL;
}
List<String> urls = parseUrls(raw);
return urls.isEmpty() ? parseUrls(DEFAULT_RELOAD_URL) : urls;
}
/**
* 콤마/세미콜론/줄바꿈으로 구분된 URL 목록을 파싱한다.
* {@code #} 로 시작하는 줄은 주석으로 무시하고, 중복 URL 은 최초 순서만 남긴다.
*/
static List<String> parseUrls(String raw) {
if (raw == null || raw.trim().isEmpty()) {
return Collections.emptyList();
}
Set<String> urls = new LinkedHashSet<>();
for (String token : raw.split("[,;\\r\\n]")) {
String url = token.trim();
if (url.isEmpty() || url.startsWith("#")) {
continue;
}
urls.add(url);
}
return new ArrayList<>(urls);
}
}
@@ -3,6 +3,7 @@ package com.eactive.eai.rms.onl.common.service;
import com.eactive.eai.rms.onl.common.service.ums.UmsDispatchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@@ -21,6 +22,7 @@ import java.util.stream.Collectors;
*/
// Cron Expression: */5 * * * * ?
@Slf4j
@DisallowConcurrentExecution
public class UmsDispatchJob implements Job {
private final UmsDispatchService umsDispatchService;
@@ -1,11 +1,13 @@
package com.eactive.eai.rms.onl.manage.comm.error;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.data.entity.onl.adapter.AdapterGroupService;
import com.eactive.eai.rms.data.entity.onl.inbound.error.InboundErrorInfoSearch;
@@ -21,6 +23,8 @@ public class InboundErrorInfoManService extends BaseService {
private AdapterGroupService adapterGroupService;
private final PersonalDataEncryptConverter encryptConverter = new PersonalDataEncryptConverter();
@Autowired
public InboundErrorInfoManService(InboundErrorInfoService inboundErrorInfoService,
InboundErrorInfoUIMapper inboundErrorInfoUIMapper, AdapterGroupService adapterGroupService) {
@@ -44,6 +48,10 @@ public class InboundErrorInfoManService extends BaseService {
});
}
if (StringUtils.isNotBlank(ui.getBzwkDataCtnt())) {
ui.setBzwkDataCtnt(encryptConverter.convertToEntityAttribute(ui.getBzwkDataCtnt()));
}
return ui;
}
@@ -14,12 +14,29 @@ class KJBInterfaceDao extends SqlMapClientTemplateDao {
private static final Logger logger = LoggerFactory.getLogger(InterfaceDao.class);
public void lazyInsertToIF01(HashMap paramMap) throws Exception {
this.template.insert("Interface.insertToIF01", paramMap);
}
public HashMap selectDetail(HashMap paramMap) throws Exception {
return (HashMap) template.queryForObject("Interface.selectDetail", paramMap);
}
public void lazyInsertToIF01(HashMap paramMap) throws Exception {
this.template.insert("Interface.insertToIF01", paramMap);
}
public HashMap selectDetailAPI(HashMap paramMap) throws Exception {
return (HashMap) template.queryForObject("Interface.selectDetailAPI", paramMap);
}
public void insertToHE01(HashMap paramMap) throws Exception {
this.template.insert("Interface.insertToHE01", paramMap);
}
public void insertToHE02(HashMap paramMap) throws Exception {
this.template.insert("Interface.insertToHE02", paramMap);
}
public void deleteToHE02(HashMap paramMap) throws Exception {
this.template.insert("Interface.deleteToHE02", paramMap);
}
}
@@ -2,9 +2,13 @@ package com.eactive.eai.rms.onl.manage.rule.layoutsync;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.common.util.UUIDGenerator;
import lombok.RequiredArgsConstructor;
@Service
@@ -14,6 +18,10 @@ class KJBInterfaceService {
private final KJBInterfaceDao dao;
@Autowired
@Qualifier("layoutSyncService")
private LayoutSyncService service;
public void lazyInsertToIF01(HashMap<String, Object> param) throws Exception {
HashMap detail = dao.selectDetail(param);
if (detail == null) {
@@ -21,4 +29,32 @@ class KJBInterfaceService {
}
}
public void insertInterface(HashMap<String, Object> info) throws Exception {
HashMap detail = dao.selectDetailAPI(info);
if (detail == null) {
dao.insertToHE01(info);
dao.deleteToHE02(info);
dao.insertToHE02(info);
HashMap<String, String> logParam = new HashMap<String,String>();
logParam.put("logPrcssSeqno", UUIDGenerator.getUUID());
logParam.put("serviceName", (String)info.get("serviceName"));
logParam.put("command", "interface");
logParam.put("loutName", (String)info.get("EAISVCNAME"));
logParam.put("recvData", (String)info.get("recvData"));
logParam.put("recvAmndHMS", (String)info.get("recvAmndHMS"));
logParam.put("prcssRslt", "S");
logParam.put("prcssRsltCmnt", "");
service.addLayoutSyncLog(logParam);
}
}
public boolean existsInterface(String interfaceId) throws Exception {
HashMap<String, Object> param = new HashMap<>();
param.put("EAISVCNAME", interfaceId);
return dao.selectDetailAPI(param) != null;
}
}
@@ -3,7 +3,6 @@ package com.eactive.eai.rms.onl.manage.rule.layoutsync;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.text.ParseException;
@@ -681,59 +680,46 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
// // 요청 전문
sis = request.getInputStream();
JSONObject json = (JSONObject) JSONValue.parse(new InputStreamReader(sis, "EUC-KR"));
// JJB 향후제거 shkim 2020-05-13 연계 테스트 체크
logger.error("\n\n MMI SYNC Request : " + json.toString());
// log 기록
byte[] arg = new byte[request.getContentLength()];
int resultSet =0;
int total = 0;
try {
resultSet = sis.readLine(arg, 0, arg.length);
while(resultSet != -1) {
total += resultSet;
resultSet = sis.readLine(arg, total, 1024);
}
String jsonContent = "";
int resultSet = sis.readLine(arg, 0, arg.length);
while(resultSet != -1) {
total += resultSet;
resultSet = sis.readLine(arg, total, 1024);
}
param.put("recvData", new String(arg, "EUC-KR"));
} catch(Exception e) {
logger.error("MessageSync.err - don't read the JSON File - " + new String(arg, "EUC-KR"), e);
param.put("recvData", "");
}
jsonContent = new String(arg, "EUC-KR");
param.put("recvData", jsonContent);
JSONObject json = (JSONObject) JSONValue.parse(jsonContent);
//syncLayout(param, recvTime, json);
syncLayoutDjb(param, recvTime, json);
// results.getChild("Result").setText("S".equals(param.get("prcssRslt"))?"True":"False");
// results.getChild("ResultMessage").setText("S".equals(param.get("prcssRslt"))?"성공":param.get("prcssRsltCmnt"));
// resultS = StringUtils.toXMLString(rDoc, true);
results.put("Result", "S".equals(param.get("prcssRslt"))?"True":"False");
results.put("ResultMessage", "S".equals(param.get("prcssRslt"))?"성공":param.get("prcssRsltCmnt"));
} catch( Exception e ) {
logger.error("", e);
try {
if( results != null ) {
// results.getChild("Result").setText("False");
// results.getChild("ResultMessage").setText( e.getMessage() );
// resultS = StringUtils.toXMLString(rDoc, true);
results.put("Result", "False");
results.put("ResultMessage", e.getMessage() );
results.put("ResultMessage", e.getMessage());
}
if( param.get("logPrcssSeqno") == null ) {
param.put("logPrcssSeqno", UUIDGenerator.getUUID());
param.put("ServiceName", "MessageSync");
param.put("RecvAmndHMS", recvTime);
param.put("serviceName", "MessageSync");
param.put("recvAmndHMS", recvTime);
}
param.put("PrcssRslt", "F");
param.put("PrcssRsltCmnt", StringUtils.cuttings(CommonUtil.getArrayString(e.getStackTrace(), "\n"), 4000)[0]);
param.put("prcssRslt", "F");
param.put("prcssRsltCmnt", e.getMessage());
//param.put("prcssRsltCmnt", StringUtils.cuttings(CommonUtil.getArrayString(e.getStackTrace(), "\n"), 4000)[0]);
service.addLayoutSyncLog(param);
} catch(Exception ee) {
ee.printStackTrace();
}
@@ -1223,6 +1209,7 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
String serviceName = (String) header.get("ServiceName");
String command = (String) header.get("Command");
String interfaceId = (String) body.get("interfaceCode");
Object layoutObject = body.get("Layout");
@@ -1230,15 +1217,90 @@ public class LayoutSyncController extends OnlBaseAnnotationController implements
param.put("command", command);
param.put("recvAmndHMS", recvTime);
boolean isDevMake = this.isDevMake(interfaceId);
if (command.equals("insert")) {
insertInterfaceDjb(interfaceId, body, param);
if (isDevMake) {
interfaceId = interfaceId.substring(0,8) + "D" + interfaceId.substring(8);
insertInterfaceDjb(interfaceId, body, param);
}
}
JSONArray layoutArray = null;
if (layoutObject instanceof JSONArray) {
layoutArray = (JSONArray) layoutObject;
for (int i = 0; i < layoutArray.size(); i++) {
syncLayout(param, body, command, (JSONObject) layoutArray.get(i));
JSONObject layout = (JSONObject) layoutArray.get(i);
syncLayout(param, body, command, layout);
if (StringUtils.isEmpty(interfaceId)) {
isDevMake = isDevMake(layout);
}
if (isDevMake) {
String layoutName = (String) layout.get("id");
layoutName = layoutName.substring(0,12) + "D" + layoutName.substring(12);
layout.put("id", layoutName);
syncLayout(param, body, command, layout);
}
}
} else {
syncLayout(param, body, command, (JSONObject) layoutObject);
JSONObject layout = (JSONObject) layoutObject;
syncLayout(param, body, command, layout);
if (StringUtils.isEmpty(interfaceId)) {
isDevMake = isDevMake(layout);
}
if (isDevMake) {
String layoutName = (String) layout.get("id");
layoutName = layoutName.substring(0,12) + "D" + layoutName.substring(12);
layout.put("id", layoutName);
syncLayout(param, body, command, layout);
}
}
}
private boolean isDevMake(String interfaceId) throws Exception {
if (StringUtils.isEmpty(interfaceId)) {
return false;
}
//interfaceId 9번째 글자가 D이면 false
if ("D".equals(interfaceId.substring(7,8))) {
return false;
}
//신규 등록 true
if (!interfaceService.existsInterface(interfaceId)) {
return true;
}
//interface는 있는데 Dev가 없으면 (필요없어서 삭제한경우) false
String devInterfaceId = interfaceId.substring(0,8) + "D" + interfaceId.substring(8);
return interfaceService.existsInterface(devInterfaceId);
}
private boolean isDevMake(JSONObject layout) throws Exception {
String loutName = (String) layout.get("id");
String[] parts = loutName.split("_");
String interfaceId = parts[1];
return isDevMake(interfaceId);
}
private void insertInterfaceDjb(String interfaceId, JSONObject body, HashMap<String, String> param) throws Exception
{
String bizCode = (String) body.get("bzSystemCode");
String interfaceDesc = (String) body.get("interfaceDesc");
HashMap<String, Object> info = new HashMap<String, Object>();
info.put("EAISVCNAME", interfaceId);
info.put("EAIBZWKDSTCD", bizCode);
info.put("EAISVCDESC", interfaceDesc);
info.put("serviceName", param.get("serviceName"));
info.put("recvData", body.toJSONString());
info.put("recvAmndHMS", param.get("recvAmndHMS"));
interfaceService.insertInterface(info);
}
}
@@ -0,0 +1,45 @@
package com.eactive.eai.rms.onl.manage.rule.layoutsync.history;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.layoutsync.history.LayoutSyncHistoryService;
import com.eactive.eai.rms.onl.manage.rule.layoutsync.history.ui.LayoutSyncHistoryUI;
import com.eactive.eai.rms.onl.manage.rule.layoutsync.history.ui.LayoutSyncHistoryUISearch;
@Controller
public class LayoutSyncHistoryController extends OnlBaseAnnotationController {
@Autowired
private LayoutSyncHistoryService service;
@GetMapping(value = "/onl/admin/rule/layoutSyncHistoryMan.view")
public void view() {
// 목록 view
}
@GetMapping(value = "/onl/admin/rule/layoutSyncHistoryMan.view", params = "cmd=DETAIL")
public String detailView() {
return "/onl/admin/rule/layoutSyncHistoryManDetail";
}
@PostMapping(value = "/onl/admin/rule/layoutSyncHistoryMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<LayoutSyncHistoryUI>> selectList(Pageable pageable,
LayoutSyncHistoryUISearch search) {
Page<LayoutSyncHistoryUI> page = service.selectList(pageable, search);
return ResponseEntity.ok(new GridResponse<>(page));
}
@PostMapping(value = "/onl/admin/rule/layoutSyncHistoryMan.json", params = "cmd=DETAIL")
public ResponseEntity<LayoutSyncHistoryUI> selectDetail(Long seqno) {
return ResponseEntity.ok(service.selectDetail(seqno));
}
}
@@ -0,0 +1,17 @@
package com.eactive.eai.rms.onl.manage.rule.layoutsync.history.ui;
import lombok.Data;
@Data
public class LayoutSyncHistoryUI {
private Long seqno;
private String serviceName;
private String command;
private String loutName;
private String recvAmndHms;
private String prcssRslt;
private String prcssRsltCmnt;
private String recvData;
}
@@ -0,0 +1,17 @@
package com.eactive.eai.rms.onl.manage.rule.layoutsync.history.ui;
import lombok.Data;
@Data
public class LayoutSyncHistoryUISearch {
private String searchLoutName;
/* 연동시간 기간 검색 (yyyyMMdd, 8자리) */
private String searchStartDate;
private String searchEndDate;
/* 처리 결과 (S/F, 공백=전체) */
private String searchPrcssRslt;
}
@@ -12,11 +12,13 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@@ -31,378 +33,329 @@ import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.login.SessionManager;
import com.eactive.eai.rms.common.spring.BaseController;
import com.eactive.eai.rms.common.spring.LocaleMessage;
import com.eactive.eai.rms.common.util.CamelCaseUtil;
import com.eactive.eai.rms.common.util.CommonUtil;
import com.eactive.eai.rms.common.util.IpUtil;
import com.eactive.eai.rms.common.vo.JsonPageVo;
import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.common.vo.SimpleResponse;
import com.eactive.eai.rms.onl.common.exception.BizException;
import com.eactive.eai.rms.onl.common.exception.EMSRuntimeException;
import com.eactive.eai.rms.onl.loader.service.HistoryService;
import com.eactive.eai.rms.onl.loader.service.TransactionLoaderService;
import com.google.gson.Gson;
import com.eactive.eai.rms.onl.manage.rule.layout.LayoutManService;
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutItemUI;
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutUI;
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutUISearch;
import com.eactive.eai.rms.onl.manage.rule.transform2.Transform2Service;
import com.eactive.eai.rms.onl.manage.rule.transform2.ui.TransformSourceResultUI;
import com.eactive.eai.rms.onl.manage.rule.transform2.ui.TransformUI;
@Controller
public class TransformController extends BaseController {
private static final Logger logger = Logger.getLogger(TransformController.class);
@Autowired
@Qualifier("localeMessage")
@Autowired
private LocaleMessage localeMessage;
@Autowired
private TransformManService service;
@Autowired
private Transform2Service service;
@Autowired
private ComboService comboService;
@Autowired
private ComboService comboService;
@Autowired
@Qualifier("historyService")
private HistoryService historyService;
@Autowired
private HistoryService historyService;
@Autowired
private TransactionLoaderService loaderService;
@Autowired
private TransactionLoaderService loaderService;
@Autowired
@Qualifier("monitoringContext")
@Autowired
private MonitoringContext monitoringContext;
@RequestMapping(value= "/onl/admin/rule/transformMan.view")
private String viewList(HttpServletRequest request, @RequestParam(value = "cmd", required = false ) String cmd ) throws Exception {
if (cmd == null || "LIST".equals(cmd.toUpperCase())){
cmd = "";
}else {
cmd = CamelCaseUtil.converCamelCase(cmd);
@Autowired
private LayoutManService layoutManService;
@GetMapping(value = "/onl/admin/rule/transformMan.view")
public String view() {
return "/onl/admin/rule/transformMan";
}
@GetMapping(value = "/onl/admin/rule/transformMan.view", params = "cmd=DETAIL")
public String detailView() {
return "/onl/admin/rule/transformManDetail";
}
@GetMapping(value = "/onl/admin/rule/transformMan.view", params = "cmd=DETAIL*POPUP")
public String detailPopupView() {
return "/onl/admin/rule/transformManDetailPopup";
}
@GetMapping(value = "/onl/admin/rule/transformMan.view", params = "cmd=LAYOUT")
public String layoutView() {
return "/onl/admin/rule/transformManLayout";
}
@GetMapping(value = "/onl/admin/rule/transformMan.view", params = "cmd=NEW")
public String newView() {
return "/onl/admin/rule/transformManNew";
}
@GetMapping(value = "/onl/admin/rule/transformMan.view", params = "cmd=POPUP CALENDAR")
public String popupCalendarView() {
return "/onl/admin/rule/transformManPopupCalendar";
}
@GetMapping(value = "/onl/admin/rule/transformMan.view", params = "cmd=API_POPUP")
public String apiPopupView() {
return "/onl/admin/rule/transformManApiPopup";
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_INIT_COMBO")
public ResponseEntity<Map<String, Object>> initCombo(HttpServletRequest request, String eaiSvcName, String cnvsnName) {
String roleId = SessionManager.getRoleIdString(request);
String isAdmin = "N";
if (roleId.indexOf("admin") > -1) {
// admin 일경우
isAdmin = "Y";
}
String path = request.getServletPath();
return path.split("[.]")[0]+cmd;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_INIT_COMBO")
public ModelAndView initCombo( HttpServletRequest request,
HttpServletResponse response,String eaiSvcName) throws Exception {
//admin 권한
String roleId = SessionManager.getRoleIdString(request);
String isAdmin = "N";
if(roleId.indexOf("admin") > -1){ //admin 일경우
isAdmin = "Y";
}
List<ComboVo> listFunction = comboService.getFromFunction();
List<ComboVo> listFunction = comboService.getFromFunction();
List<ComboVo> listTransform = comboService.getFromTransform(eaiSvcName, cnvsnName);
List listTransform = service.selectTransformForEaiSvcNameCombo(eaiSvcName);
List<ComboVo> listCnvsnType = comboService.getFromCode("CNVSN_TYPE");
List<ComboVo> listSvrDstcd = comboService.getFromCode("INOUT_TYPE");
List<ComboVo> listUseYn = comboService.getFromCode("USE_YN");// 사용여부
Map resultMap = new HashMap();
resultMap.put("functionRows" , listFunction);
resultMap.put("transformRows" , listTransform);
resultMap.put("cnvsnType" , listCnvsnType);
resultMap.put("svrDstcd" , listSvrDstcd);
resultMap.put("isAdmin" , isAdmin);
resultMap.put("useYnRows" , listUseYn);
List<ComboVo> listCnvsnType = comboService.getFromCode("CNVSN_TYPE");
List<ComboVo> listSvrDstcd = comboService.getFromCode("INOUT_TYPE");
List<ComboVo> listUseYn = comboService.getFromCode("USE_YN");
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_EAISVCNAME_COMBO")
public ModelAndView eaiSvcNameCombo( HttpServletRequest request,
HttpServletResponse response) throws Exception {
// String eaiBizCode = RecordAuthUtil.getRecordAuthQuery(request);
List listCnvsnType = comboService.getFromCode("CNVSN_TYPE");
List<ComboVo> listInoutTypeCd = comboService.getFromCode("INOUT_TYPE");//EAI서버구분코드
List<ComboVo> listUseYn = comboService.getFromCode("USE_YN");
Map resultMap = new HashMap();
resultMap.put("cnvsnTypeRows" , listCnvsnType);
resultMap.put("inoutTypeRows" , listInoutTypeCd);
resultMap.put("useYnRows" , listUseYn);
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
}
// @RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_LAYOUT_COMBO")
// public ModelAndView layoutCombo( HttpServletRequest request,
// HttpServletResponse response) throws Exception {
//
// String eaiBizCode = RecordAuthUtil.getRecordAuthQuery(request);
// List list = service.selectLayoutCombo(eaiBizCode);
//
// Map resultMap = new HashMap();
// resultMap.put("rows" , list);
//
// ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
// return modelAndView;
// }
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_FUNCTION_COMBO")
public ModelAndView functionCombo( HttpServletRequest request,
HttpServletResponse response) throws Exception {
List list = service.selectFunctionCombo();
Map resultMap = new HashMap();
resultMap.put("rows" , list);
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_TRANSFORM4EAISVCNAME_COMBO")
public ModelAndView transformForEaiSvcNameCombo( HttpServletRequest request,
HttpServletResponse response, String eaiSvcName) throws Exception {
List list = service.selectTransformForEaiSvcNameCombo(eaiSvcName);
Map resultMap = new HashMap();
resultMap.put("rows" , list);
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_LAYOUT_ITEM")
public ModelAndView listLayoutItem(HttpServletRequest request, HttpServletResponse response,String loutName) throws Exception {
List list = service.selectLayoutItems(loutName);
HashMap map = new HashMap();
map.put("rows", list);
ModelAndView modelAndView = new ModelAndView("jsonView",map);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST")
public ModelAndView list( HttpServletRequest request,
HttpServletResponse response,JsonPageVo pageVo,
String searchBizCode, String searchCnvsnName,String searchCnvsnDesc) throws Exception {
//String eaiBizCode = RecordAuthUtil.getRecordAuthQuery(request);
HashMap map = service.selectList(pageVo.getStartNum(), pageVo.getEndNum(),searchBizCode, searchCnvsnName,searchCnvsnDesc);
pageVo.setTotalCount((Integer)map.get("totalCount"));
Map resultMap = new HashMap();
resultMap.put("total" , pageVo.getTotal()); //전체 페이지수
resultMap.put("page" , pageVo.getPage()); //현재 페이지수
resultMap.put("records", pageVo.getTotalCount());//전체 레코드건수
resultMap.put("rows" , map.get("list"));
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
}
// @RequestMapping(value= "/onl/admin/common/codeMan.json",params = "cmd=DETAIL")
// private ModelAndView detail(HttpServletRequest request,
// HttpServletResponse response,String msg) throws Exception {
//
// HashMap map = transformManService.selectDetail(msg);
//
// ModelAndView modelAndView = new ModelAndView("jsonView",map);
//
// return modelAndView;
// }
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=INSERT")
private String insert(HttpServletRequest request,
HttpServletResponse response, @RequestParam HashMap param) throws Exception {
service.insert(param);
String cnvsnName = (String)param.get("cnvsnName");
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.ReloadTransformCommand",cnvsnName);
agentUtilService.broadcast(command);
String eaiSvcName = (String)param.get("eaiSvcName");
if(eaiSvcName != null && !"".equals(eaiSvcName)){
command = new CommonCommand("com.eactive.eai.agent.eaimessage.ReloadEAIMessageCommand",eaiSvcName);
agentUtilService.broadcast(command);
}
return null;
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("functionRows", listFunction);
resultMap.put("transformRows", listTransform);
resultMap.put("cnvsnType", listCnvsnType);
resultMap.put("svrDstcd", listSvrDstcd);
resultMap.put("isAdmin", isAdmin);
resultMap.put("useYnRows", listUseYn);
return ResponseEntity.ok(resultMap);
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=DELETE")
private String delete(HttpServletRequest request,
HttpServletResponse response, String cnvsnName,String eaiSvcName,String cnvsnType ) throws Exception {
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_EAISVCNAME_COMBO")
public ResponseEntity<Map<String, Object>> eaiSvcNameCombo(HttpServletRequest request,
HttpServletResponse response) {
service.delete(cnvsnName,eaiSvcName,cnvsnType);
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.RemoveTransformCommand",cnvsnName);
agentUtilService.broadcast(command);
if(eaiSvcName != null && !"".equals(eaiSvcName)){
command = new CommonCommand("com.eactive.eai.agent.eaimessage.ReloadEAIMessageCommand",eaiSvcName);
agentUtilService.broadcast(command);
}
return null;
List<ComboVo> listCnvsnType = comboService.getFromCode("CNVSN_TYPE");
List<ComboVo> listInoutTypeCd = comboService.getFromCode("INOUT_TYPE");
List<ComboVo> listUseYn = comboService.getFromCode("USE_YN");
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("cnvsnTypeRows", listCnvsnType);
resultMap.put("inoutTypeRows", listInoutTypeCd);
resultMap.put("useYnRows", listUseYn);
return ResponseEntity.ok(resultMap);
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=DELETE_LIST")
private ModelAndView deleteMulti(HttpServletRequest request,
HttpServletResponse response, @RequestParam HashMap<String,Object> map ) throws Exception {
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_LAYOUT_ITEM")
public ResponseEntity<GridResponse<LayoutItemUI>> listLayoutItem(String loutName) {
List<LayoutItemUI> list = service.selectLayoutItems(loutName);
return ResponseEntity.ok(new GridResponse<>(list));
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<TransformUI>> list(Pageable pageable, String searchCnvsnName,
String searchCnvsnDesc, String searchBizCode, String searchIfSvcCd) {
Page<TransformUI> page = service
.selectList(pageable, searchCnvsnName, searchCnvsnDesc, searchBizCode, searchIfSvcCd);
return ResponseEntity.ok(new GridResponse<>(page));
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=INSERT")
public ResponseEntity<Void> insert(TransformUI transformUI) {
service.insert(transformUI);
CommonCommand
.builder()
.name(CommonCommand.RELOAD_TRANSFORM_COMMAND)
.args(transformUI.getCnvsnName())
.build()
.broadcast(agentUtilService);
String eaiSvcName = transformUI.getEaiSvcName();
if (eaiSvcName != null && !"".equals(eaiSvcName)) {
CommonCommand
.builder()
.name(CommonCommand.RELOAD_EAI_MESSAGE_COMMAND)
.args(eaiSvcName)
.build()
.broadcast(agentUtilService);
}
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=DELETE")
public ResponseEntity<Void> delete(String cnvsnName, String eaiSvcName, String cnvsnType) {
service.delete(cnvsnName, eaiSvcName, cnvsnType);
CommonCommand
.builder()
.name(CommonCommand.REMOVE_TRANSFORM_COMMAND)
.args(cnvsnName)
.build()
.broadcast(agentUtilService);
if (eaiSvcName != null && !"".equals(eaiSvcName)) {
CommonCommand
.builder()
.name(CommonCommand.RELOAD_EAI_MESSAGE_COMMAND)
.args(eaiSvcName)
.build()
.broadcast(agentUtilService);
}
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=DELETE_LIST")
public ResponseEntity<SimpleResponse> deleteMulti(String[] cnvsnNames, String[] eaiSvcNames, String[] cnvsnTypes) {
String message = "";
String[] cnvsnNameArr = ((String) map.get("cnvsnNames")).split(",");
String eaiSvcNamesString = (String) map.get("eaiSvcNames") + ",";
String cnvsnTypesString = (String) map.get("cnvsnTypes") + ",";
String[] eaiSvcNameArr = StringUtils.splitPreserveAllTokens(eaiSvcNamesString, ",");
String[] cnvsnTypeArr = StringUtils.splitPreserveAllTokens(cnvsnTypesString, ",");
int idx = 0;
for(String cnvsnName : cnvsnNameArr){
service.delete(cnvsnName,eaiSvcNameArr[idx],cnvsnTypeArr[idx]);
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.RemoveTransformCommand",cnvsnName);
agentUtilService.broadcast(command);
if(eaiSvcNameArr[idx] != null && !"".equals(eaiSvcNameArr[idx])){
command = new CommonCommand("com.eactive.eai.agent.eaimessage.ReloadEAIMessageCommand",eaiSvcNameArr[idx]);
agentUtilService.broadcast(command);
}
idx++;
for (String cnvsnName : cnvsnNames) {
if(StringUtils.isBlank(eaiSvcNames[idx])){
service.delete(cnvsnName, "", cnvsnTypes[idx]);
} else {
service.delete(cnvsnName, eaiSvcNames[idx], cnvsnTypes[idx]);
}
CommonCommand
.builder()
.name(CommonCommand.REMOVE_TRANSFORM_COMMAND)
.args(cnvsnName)
.build()
.broadcast(agentUtilService);
if (eaiSvcNames[idx] != null && !"".equals(eaiSvcNames[idx])) {
CommonCommand
.builder()
.name(CommonCommand.RELOAD_EAI_MESSAGE_COMMAND)
.args(eaiSvcNames[idx])
.build()
.broadcast(agentUtilService);
}
idx++;
}
message = ""+idx+"건을 삭제하였습니다";
Map resultMap = new HashMap();
resultMap.put("message" , message);
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_TRANSFORM_SOURCE_RESULT")
public ModelAndView transformSourceResult( HttpServletRequest request,
HttpServletResponse response,String cnvsnName) throws Exception {
List list = service.selectTransformSourceResult(cnvsnName );
HashMap map = new HashMap();
map.put("rows", list);
ModelAndView modelAndView = new ModelAndView("jsonView",map);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_TRANSFORM_ITEM")
public ModelAndView transformItem( HttpServletRequest request,
HttpServletResponse response,String cnvsnName,String loutName) throws Exception {
// String eaiBizCode = RecordAuthUtil.getRecordAuthQuery(request);
List list = service.selectTransformItems(cnvsnName ,loutName );
HashMap map = new HashMap();
map.put("rows", list);
ModelAndView modelAndView = new ModelAndView("jsonView",map);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=UPDATE")
public ModelAndView update( HttpServletRequest request,
HttpServletResponse response, @RequestParam HashMap<String,Object> paramMap ) throws Exception {
String gridData = (String)paramMap.get("gridData");
Gson gson = new Gson();
HashMap[] list = gson.fromJson(gridData, HashMap[].class);
paramMap.put("list", list);
service.transaction(paramMap);
String cnvsnName = (String)paramMap.get("cnvsnName");
String eaiSvcName = (String)paramMap.get("eaiSvcName");
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.ReloadTransformCommand",cnvsnName);
agentUtilService.broadcast(command);
if(eaiSvcName != null && !"".equals(eaiSvcName)){
command = new CommonCommand("com.eactive.eai.agent.eaimessage.ReloadEAIMessageCommand",eaiSvcName);
agentUtilService.broadcast(command);
}
return null;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=INITIALIZES")
public ModelAndView initializes(HttpServletRequest request,
HttpServletResponse response) throws Exception {
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.ReloadTransformCommand","ALL");
HashMap returnMap = agentUtilService.broadcast(command);
HashMap map = new HashMap();
map.put("message", CommonUtil.getMapString(returnMap, true));
ModelAndView modelAndView = new ModelAndView("jsonView", map);
return modelAndView;
message = "" + idx + "건을 삭제하였습니다";
return ResponseEntity.ok(SimpleResponse.of(message));
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=INITIALIZE")
public ModelAndView initialize(HttpServletRequest request,
HttpServletResponse response, String cnvsnName) throws Exception {
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.ReloadTransformCommand",cnvsnName);
HashMap returnMap = agentUtilService.broadcast(command);
HashMap map = new HashMap();
map.put("message", CommonUtil.getMapString(returnMap, true));
ModelAndView modelAndView = new ModelAndView("jsonView", map);
return modelAndView;
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_TRANSFORM_SOURCE_RESULT")
public ResponseEntity<GridResponse<TransformSourceResultUI>> transformSourceResult(String cnvsnName) {
List<TransformSourceResultUI> list = service.selectTransformSourceResult(cnvsnName);
return ResponseEntity.ok(new GridResponse<>(list));
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=LIST_INITIALIZE")
public ModelAndView listInitialize(HttpServletRequest request,
HttpServletResponse response, @RequestParam HashMap<String,Object> paramMap) throws Exception {
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_TRANSFORM_ITEM")
public ResponseEntity<GridResponse<LayoutItemUI>> transformItem(String cnvsnName, String loutName,
String srcLayoutName) {
List<LayoutItemUI> list = service.selectTransformItems(cnvsnName, loutName, srcLayoutName);
return ResponseEntity.ok(new GridResponse<>(list));
}
String[] chkItemsArr = ((String) paramMap.get("chkItems")).split(",");
Vector vec = new Vector<>();
for(String chkItem : chkItemsArr){
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=UPDATE")
public ResponseEntity<Void> update(TransformUI transformUI) {
service.transaction(transformUI);
CommonCommand
.builder()
.name(CommonCommand.RELOAD_TRANSFORM_COMMAND)
.args(transformUI.getCnvsnName())
.build()
.broadcast(agentUtilService);
if (StringUtils.isNotBlank(transformUI.getEaiSvcName())) {
CommonCommand
.builder()
.name(CommonCommand.RELOAD_EAI_MESSAGE_COMMAND)
.args(transformUI.getEaiSvcName())
.build()
.broadcast(agentUtilService);
}
return ResponseEntity.ok().build();
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=INITIALIZES")
public ResponseEntity<SimpleResponse> initializes() {
String message = CommonCommand
.builder()
.name(CommonCommand.RELOAD_TRANSFORM_COMMAND)
.args("ALL")
.build()
.broadcast(agentUtilService)
.getMessage(true);
return ResponseEntity.ok(SimpleResponse.of(message));
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=INITIALIZE")
public ResponseEntity<SimpleResponse> initialize(String cnvsnName) {
String message = CommonCommand
.builder()
.name(CommonCommand.RELOAD_TRANSFORM_COMMAND)
.args(cnvsnName)
.build()
.broadcast(agentUtilService)
.getMessage(true);
return ResponseEntity.ok(SimpleResponse.of(message));
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_INITIALIZE")
public ResponseEntity<SimpleResponse> listInitialize(String[] chkItems) {
Vector<String> vec = new Vector<>();
for (String chkItem : chkItems) {
vec.add(chkItem);
}
CommonCommand command = new CommonCommand("com.eactive.eai.agent.transformer.ReloadTransformListCommand",vec);
HashMap returnMap = agentUtilService.broadcast(command);
HashMap map = new HashMap();
map.put("message", CommonUtil.getMapString(returnMap, true));
ModelAndView modelAndView = new ModelAndView("jsonView", map);
return modelAndView;
}
@RequestMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_LAYOUT")
private ModelAndView selectLoutList(HttpServletRequest request,
JsonPageVo pageVo , @RequestParam HashMap<String,Object> paramMap ) throws Exception {
HashMap map = service.selectLayoutList( pageVo.getStartNum(), pageVo.getEndNum(), paramMap);
pageVo.setTotalCount((Integer)map.get("totalCount"));
Map resultMap = new HashMap();
resultMap.put("total" , pageVo.getTotal()); //전체 페이지수
resultMap.put("page" , pageVo.getPage()); //현재 페이지수
resultMap.put("records", pageVo.getTotalCount());//전체 레코드건수
resultMap.put("rows" , map.get("list"));
ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
return modelAndView;
String message = CommonCommand
.builder()
.name(CommonCommand.RELOAD_TRANSFORM_LIST_COMMAND)
.args(vec)
.build()
.broadcast(agentUtilService)
.getMessage(true);
return ResponseEntity.ok(SimpleResponse.of(message));
}
@RequestMapping(value="/onl/admin/rule/transformMan.json", params="cmd=CHANGE_EXPORT")
public ModelAndView exportData(HttpServletRequest request, @RequestParam HashMap<String,Object> paramMap) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=LIST_LAYOUT")
public ResponseEntity<GridResponse<LayoutUI>> selectLoutList(Pageable pageable, LayoutUISearch layoutUISearch) {
Page<LayoutUI> page = layoutManService.selectList(pageable, layoutUISearch);
return ResponseEntity.ok(new GridResponse<>(page));
}
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=CHANGE_EXPORT")
public ModelAndView exportData(HttpServletRequest request, @RequestParam HashMap<String, Object> paramMap)
throws Exception {
HashMap<String, Object> resultMap = new HashMap<>();
String userId = SessionManager.getUserId(request);
if (!SystemUtil.isDevServer()){
throw new Exception("only support dev server ");
}
try {
String cnvsnNames = (String)paramMap.get("cnvsnNames");
//취약점소스 수정 2020.08.25
String[] arrCnvsnNames = null;
if(cnvsnNames != null){
arrCnvsnNames = cnvsnNames.split("\\,");
paramMap.put("userId", userId);
paramMap.put("arrCnvsnNames", arrCnvsnNames);
if (!SystemUtil.isDevServer()) {
throw new EMSRuntimeException("only support dev server ");
}
try {
String cnvsnNames = (String) paramMap.get("cnvsnNames");
// 취약점소스 수정 2020.08.25
String[] arrCnvsnNames = null;
if (cnvsnNames != null) {
arrCnvsnNames = cnvsnNames.split("\\,");
paramMap.put("userId", userId);
paramMap.put("arrCnvsnNames", arrCnvsnNames);
service.exportData(paramMap);
}
service.exportData(paramMap);
}
resultMap.put("message", "전송이 성공적으로 완료되었습니다");
} catch (BizException be) {
} catch (BizException be) {
throw new Exception(be.getMessage());
} catch (Exception e) {
logger.error(e.getMessage());
@@ -412,74 +365,33 @@ public class TransformController extends BaseController {
ModelAndView modelAndView = new ModelAndView("jsonView", resultMap);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.excel",params = "cmd=LIST_GRID_TO_EXCEL")
public ModelAndView listGridToExcel(HttpServletRequest request, HttpServletResponse response, @RequestParam HashMap<String,Object> paramMap) throws Exception {
HashMap<String,Object> resultMap = new HashMap<String,Object>();
@PostMapping(value = "/onl/admin/rule/transformMan.excel", params = "cmd=LIST_GRID_TO_EXCEL")
public ModelAndView listGridToExcel(String searchCnvsnName, String searchCnvsnDesc, String searchBizCode,
String searchIfSvcCd) throws Exception {
HashMap<String, Object> resultMap = new HashMap<>();
List<Map<String, Object>> datas = service
.selectListToExcel(searchCnvsnName, searchCnvsnDesc, searchBizCode, searchIfSvcCd);
resultMap.put("datas", datas);
HashMap<String, String>[] list2 ;
Gson gson = new Gson();
if(paramMap.get("gridData")==null){
return new ModelAndView("excelView", resultMap);
}
String headersStr = (String) paramMap.get("headersStr");
headersStr = headersStr.substring(0, headersStr.length()-1);
paramMap.put("headersStr", headersStr);
@SuppressWarnings("unchecked")
HashMap<String, Object> list = service.selectListToExcel(request,paramMap);
String jsonStr = gson.toJson(list.get("list"));
list2 = gson.fromJson(jsonStr, HashMap[].class);
}else{
list2=gson.fromJson((String) paramMap.get("gridData"), HashMap[].class);
}
String[] headers = gson.fromJson((String) paramMap.get("header"), String[].class);
String[] headerTitles = gson.fromJson((String)paramMap.get("headerTitle"), String[].class);
HashMap<String, Object>[] lmerge = null;
String merge = (String)paramMap.get("merge");
if (merge !=null){
lmerge = gson.fromJson(merge, HashMap[].class);
}
resultMap.put("headers", headers);
resultMap.put("headerTitles", headerTitles);
resultMap.put("datas", list2);
resultMap.put("fileName", (String)paramMap.get("fileName"));
resultMap.put("merge", lmerge);
ModelAndView modelAndView = new ModelAndView("excelView",resultMap);
return modelAndView;
}
@RequestMapping(value= "/onl/admin/rule/transformMan.json",params = "cmd=DETAIL_POP")
public ModelAndView selectCnvsnDetailPop( HttpServletRequest request, HttpServletResponse response, String cnvsnName,String tgt_layout,String src_layout) throws Exception {
List list = service.selectCnvsnDetailPop(cnvsnName ,tgt_layout,src_layout);
HashMap map = new HashMap();
map.put("rows", list);
ModelAndView modelAndView = new ModelAndView("jsonView",map);
return modelAndView;
}
@RequestMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=CLONETOPROD")
private ModelAndView cloneToProd(HttpServletRequest request, @RequestParam HashMap<String, Object> paramMap)
@PostMapping(value = "/onl/admin/rule/transformMan.json", params = "cmd=CLONETOPROD")
public ModelAndView cloneToProd(HttpServletRequest request, @RequestParam HashMap<String, Object> paramMap)
throws Exception {
if (SystemUtil.isProdServer()) {
throw new Exception("only support dev, stage server");
throw new EMSRuntimeException("only support dev, stage server");
}
HashMap<String, Object> resultMap = new HashMap<String, Object>();
HashMap<String, Object> resultMap = new HashMap<>();
String userId = SessionManager.getUserId(request);
String serviceKey = SessionManager.getServiceTypeKey(request);
String cmTitle = MapUtils.getString(paramMap, "CmTitle", "Deploy Layout");
String cmTitle = MapUtils.getString(paramMap, "CmTitle", "Deploy Layout Mapping");
String cmDate = StringUtil.getNullStr(paramMap.get("CmDate"));
String cmTime = StringUtil.getNullStr(paramMap.get("CmTime"));
String ListTransformName = StringUtil.getNullStr(paramMap.get("ListTransformName"));
String[] eaiCnvsnNames = ListTransformName.split("\\,");
String listTransformName = StringUtil.getNullStr(paramMap.get("ListTransformName"));
String[] transformNames = listTransformName.split("\\,");
String downloadPath = monitoringContext.getStringProperty(MonitoringContext.IOMAP_DOWNLOAD_PATH);
downloadPath = StringUtils.removeEnd(downloadPath, File.separator) + File.separator + serviceKey
+ File.separator;
@@ -496,7 +408,7 @@ public class TransformController extends BaseController {
logger.info("serviceKey=" + serviceKey);
logger.info("cmDate=" + cmDate);
logger.info("cmTime=" + cmTime);
logger.info("ListTransformName=" + ListTransformName);
logger.info("listTransformName=" + listTransformName);
logger.info("downloadPath=" + downloadPath);
logger.info("targetName=" + targetName); // 스테이징, 운영서버
logger.info("targetUrl=" + targetUrl); // http://127.0.0.1:20100
@@ -507,8 +419,8 @@ public class TransformController extends BaseController {
String items = "";
// 0. 서브 배포 목록 확보
HashMap<String, ArrayList<HashMap<String, String>>> listMap = historyService.userHistoryListByTransform(dt,
eaiCnvsnNames);
HashMap<String, ArrayList<HashMap<String, String>>> listMap = historyService
.userHistoryListByTransform(dt, transformNames);
if (listMap != null && listMap.size() > 0) {
// 1. 파일 생성
@@ -516,20 +428,20 @@ public class TransformController extends BaseController {
loaderService.makeDataFiles(dt, downloadPath, listMap);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new Exception("Make Datafile error:" + e.getMessage());
throw new EMSRuntimeException("Make Datafile error:" + e.getMessage());
}
// 2. 파일 배포
try {
if (StringUtils.isBlank(targetUrl)) {
throw new Exception("Set EMS property:" + MonitoringContext.IOMAP_DEPLOY_TARGET_URL);
throw new EMSRuntimeException("Set EMS property:" + MonitoringContext.IOMAP_DEPLOY_TARGET_URL);
}
String url = targetUrl + "/monitoring/loader.do";
loaderService.deployDataToTargetSystem(url, downloadPath, listMap, serviceKey);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new Exception("Deploy to target System(" + targetName + ") error:" + e.getMessage());
throw new EMSRuntimeException("Deploy to target System(" + targetName + ") error:" + e.getMessage());
}
items += historyService.parseSimple(serviceKey, "", listMap);
@@ -541,7 +453,7 @@ public class TransformController extends BaseController {
loaderService.scheduleDeployDataApply(url, userId, userIp, serviceKey, cmTitle, cmDate, cmTime, items);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new Exception("Apply DeployData or Apply Schedule error:" + e.getMessage());
throw new EMSRuntimeException("Apply DeployData or Apply Schedule error:" + e.getMessage());
}
resultMap.put("message", localeMessage.getMessage("ruleDeploy.msg4"));
@@ -97,10 +97,13 @@ public class EaiMsgManService extends OnlBaseService {
EAIMessageEntity eaiMessageEntity = tuple.get(QEAIMessageEntity.eAIMessageEntity);
String nonMotivRoutUriName = tuple.get(QRouting.routing.nonmotivrouturiname);
String psvIntfaCdSticName = tuple.get(QServiceMessageEntity.serviceMessageEntity.psvintfacdsticname);
String psvSysAdptrBzwkGroupName = tuple
.get(QServiceMessageEntity.serviceMessageEntity.psvsysadptrbzwkgroupname);
EAIMessageUI eaiMessageUI = eaiMessageUIMapper.toVoWithoutChildren(eaiMessageEntity);
eaiMessageUI.setNonMotivRoutUriName(nonMotivRoutUriName);
eaiMessageUI.setPsvIntfaCdSticName(psvIntfaCdSticName);
eaiMessageUI.setPsvSysAdptrBzwkGroupName(psvSysAdptrBzwkGroupName);
return eaiMessageUI;
});
}
@@ -114,16 +117,14 @@ public class EaiMsgManService extends OnlBaseService {
Map<String, Object> map = new HashMap<>();
EAIMessageEntity eaiMessageEntity = tuple.get(QEAIMessageEntity.eAIMessageEntity);
String nonMotivRoutUriName = tuple.get(QRouting.routing.nonmotivrouturiname);
String psvIntfaCdSticName = tuple.get(QServiceMessageEntity.serviceMessageEntity.psvintfacdsticname);
String psvSysAdptrBzwkGroupName = tuple
.get(QServiceMessageEntity.serviceMessageEntity.psvsysadptrbzwkgroupname);
map.put("EAIBZWKDSTCD", eaiMessageEntity.getEaibzwkdstcd());
map.put("EAISVCNAME", eaiMessageEntity.getEaisvcname());
map.put("EAISVCDESC", eaiMessageEntity.getEaisvcdesc());
map.put("SVCMOTIVUSEDSTCD", eaiMessageEntity.getSvcmotivusedstcd());
map.put("PSVINTFACDSTICNAME", psvIntfaCdSticName);
map.put("NONMOTIVROUTURINAME", nonMotivRoutUriName);
map.put("FLOWCTRLROUTNAME", eaiMessageEntity.getFlowctrlroutname());
map.put("GSTATSYSADPTRBZWKGROUPNAME", eaiMessageEntity.getGstatsysadptrbzwkgroupname());
map.put("PSVSYSADPTRBZWKGROUPNAME", psvSysAdptrBzwkGroupName);
String eaisevrdstcd = codeService
.findCodeName("INOUT_TYPE", eaiMessageEntity.getEaisevrdstcd())
@@ -109,6 +109,9 @@ public class EAIMessageUI {
@JsonProperty("PSVINTFACDSTICNAME")
private String psvIntfaCdSticName;
@JsonProperty("PSVSYSADPTRBZWKGROUPNAME")
private String psvSysAdptrBzwkGroupName;
@JsonProperty("svcAll")
private List<ServiceMessageUI> serviceMessages;
}
@@ -1,8 +1,10 @@
package com.eactive.eai.rms.onl.transaction.apim;
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
import com.eactive.eai.agent.command.Command;
import com.eactive.eai.agent.command.CommonCommand;
import com.eactive.eai.rms.common.base.OnlBaseAnnotationController;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.login.SessionManager;
import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.onl.authserver.ClientEntityService;
@@ -24,13 +26,22 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -41,24 +52,52 @@ public class ApiInterfaceController extends OnlBaseAnnotationController {
public static final Log logger = LogFactory.getLog(ApiInterfaceController.class);
// v1 "API 스펙"(모달) 버튼 노출 여부. PTL_PROPERTY 그룹 'Portal'. 미설정 시 true(현행 노출 유지)로 생성.
private static final String API_SPEC_V1_ENABLED_KEY = "djb.apispec.v1.enabled";
private final ApiInterfaceService service;
private final ClientEntityService clientEntityService;
private final PortalPropertyService portalPropertyService;
private final MonitoringContext monitoringContext;
@Autowired
public ApiInterfaceController(ApiInterfaceService service, ClientEntityService clientEntityService) {
public ApiInterfaceController(ApiInterfaceService service, ClientEntityService clientEntityService,
PortalPropertyService portalPropertyService, MonitoringContext monitoringContext) {
this.service = service;
this.clientEntityService = clientEntityService;
this.portalPropertyService = portalPropertyService;
this.monitoringContext = monitoringContext;
}
@GetMapping(value = "/onl/transaction/apim/apiInterfaceMan.view")
public void view() {
}
@GetMapping(value = "/onl/transaction/apim/apiInterfaceMan.view", params = "cmd=DEPLOY_POPUP")
public String deployPopupView() {
return "/onl/transaction/apim/apiInterfaceDeployPopup";
}
@GetMapping(value = "/onl/transaction/apim/apiInterfaceMan.view", params = "cmd=DETAIL")
public String viewDetail() {
public String viewDetail(Model model) {
model.addAttribute("apiSpecV1Enabled", resolveApiSpecV1Enabled());
return "/onl/transaction/apim/apiInterfaceManDetail";
}
/** v1 API 스펙 버튼 노출 여부 조회(레거시 Y/N 값도 허용). 미설정/파싱불가 시 true. */
private boolean resolveApiSpecV1Enabled() {
String v = portalPropertyService.getOrCreateProperty("Portal", API_SPEC_V1_ENABLED_KEY, "true",
"API 인터페이스 상세의 v1 'API 스펙'(모달) 버튼 노출 여부(true/false)");
if (v == null) {
return true;
}
String s = v.trim();
if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("N")) {
return false;
}
return true;
}
@PostMapping(value= "/onl/transaction/apim/apiInterfaceMan.json", params = "cmd=LIST")
public ResponseEntity<GridResponse<ApiInterfaceUI>> selectList(EAIMessageUISearch eaiMessageUISearch,
@RequestParam(value = "sortName", required = false, defaultValue = "lastModifiedDate") String sortname,
@@ -289,4 +328,64 @@ public class ApiInterfaceController extends OnlBaseAnnotationController {
throw new BizException("오류가 발생했습니다. 확인바랍니다. 오류메시지: "+e.getMessage());
}
}
/**
* 모니터링 프로퍼티 iomap.upload.path 경로 하위의 {업무그룹}/{인터페이스명}.json 파일 목록을 조회한다.
*/
@PostMapping(value = "/onl/transaction/apim/apiInterfaceMan.json", params = "cmd=LIST_DEPLOY_FILES")
public ResponseEntity<Map<String, Object>> listDeployFiles() {
List<Map<String, String>> rows = new ArrayList<>();
String uploadPath = monitoringContext.getStringProperty(MonitoringContext.IOMAP_UPLOAD_PATH);
if (StringUtils.isNotBlank(uploadPath)) {
File uploadDir = new File(uploadPath);
// 업무그룹 없이 iomap.upload.path 바로 하위에 있는 {인터페이스}.json
File[] rootJsonFiles = uploadDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".json"));
if (rootJsonFiles != null) {
Arrays.sort(rootJsonFiles, Comparator.comparing(File::getName));
for (File jsonFile : rootJsonFiles) {
Map<String, String> row = new HashMap<>();
row.put("bizCode", "");
row.put("interfaceId", FilenameUtils.getBaseName(jsonFile.getName()));
row.put("filePath", jsonFile.getName());
row.put("createdDate", getFileCreatedDate(jsonFile));
rows.add(row);
}
}
File[] bizDirs = uploadDir.listFiles(File::isDirectory);
if (bizDirs != null) {
Arrays.sort(bizDirs, Comparator.comparing(File::getName));
for (File bizDir : bizDirs) {
File[] jsonFiles = bizDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".json"));
if (jsonFiles == null) {
continue;
}
Arrays.sort(jsonFiles, Comparator.comparing(File::getName));
for (File jsonFile : jsonFiles) {
Map<String, String> row = new HashMap<>();
row.put("bizCode", bizDir.getName());
row.put("interfaceId", FilenameUtils.getBaseName(jsonFile.getName()));
row.put("filePath", bizDir.getName() + File.separator + jsonFile.getName());
row.put("createdDate", getFileCreatedDate(jsonFile));
rows.add(row);
}
}
}
}
Map<String, Object> result = new HashMap<>();
result.put("rows", rows);
return ResponseEntity.ok(result);
}
private String getFileCreatedDate(File file) {
try {
BasicFileAttributes attrs = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(attrs.creationTime().toMillis()));
} catch (Exception e) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified()));
}
}
}
@@ -1160,15 +1160,19 @@ public class ApiInterfaceService extends OnlBaseService {
transformUI.setCnvsnName(updatedString);
// 치환 전 원본 레이아웃명을 먼저 보존 (CNVSNCMDNAME/CNVSNRSULTITEMPATHNAME 치환에 사용)
String oldSrcLayoutName = transformUI.getSrcLayoutName();
String oldTgtLayoutName = transformUI.getTgtLayoutName();
transformUI.setSrcLayoutName(srcLayoutName);
transformUI.setTgtLayoutName(tgtLayoutName);
for (TransformItemUI transformItemUI : transformUI.getGridData()) {
transformItemUI.setCnvsnName(updatedString);
String tgtPath = transformItemUI.getCnvsnRsultItemPathName().replaceFirst("^[^.]+", tgtLayoutName);
String tgtPath = replaceLayoutNamePrefix(transformItemUI.getCnvsnRsultItemPathName(), oldTgtLayoutName, tgtLayoutName);
transformItemUI.setCnvsnRsultItemPathName(tgtPath);
String srcPath = transformItemUI.getCnvsncmdname().replaceFirst("^[^.]+", srcLayoutName);
String srcPath = replaceLayoutNamePrefix(transformItemUI.getCnvsncmdname(), oldSrcLayoutName, srcLayoutName);
transformItemUI.setCnvsncmdname(srcPath);
}
@@ -1177,6 +1181,20 @@ public class ApiInterfaceService extends OnlBaseService {
return updatedString;
}
/*
* CNVSNCMDNAME/CNVSNRSULTITEMPATHNAME 안의 레이아웃명 부분만 정확히 치환한다.
* "^[^.]+" 로 문자열 맨 앞을 무조건 잘라내는 방식은 FUNC(LAYOUT.FIELD) 처럼 함수가 적용된 값에서
* 함수명까지 잘라먹고, 여러 항목(FUNC(LAYOUT.A, LAYOUT.B))에서는 첫 번째만 치환되는 문제가 있어
* 문자열 전체에서 "레이아웃명." 형태로 나타나는 자리만 찾아 바꾼다.
*/
private String replaceLayoutNamePrefix(String value, String oldLayoutName, String newLayoutName) {
if (value == null || oldLayoutName == null || oldLayoutName.isEmpty()) {
return value;
}
String regex = "(?<![\\w-])" + Pattern.quote(oldLayoutName) + "(?=\\.)";
return value.replaceAll(regex, Matcher.quoteReplacement(newLayoutName));
}
public ApiInterfaceUI cloneApiInterface(ApiInterfaceUI orgApiInterfaceUI, String newBizCd,
String newApiInterfaceId, String newInboundHttpMethod,
String newInboundRestPath, String newSvcDesc, boolean importDupliCheck) throws Exception {
@@ -343,7 +343,7 @@ public class ApiSpecManService {
String defaultValue = item.getLoutItemDefault();
String fieldName = item.getLoutItemName();
boolean isInteger = isIntegerType(dataType);
boolean isInteger = isIntegerType(dataType, item.getLoutItemDecimal());
if (isNumericType(dataType)) {
if (StringUtils.isNotEmpty(defaultValue)) {
@@ -393,7 +393,7 @@ public class ApiSpecManService {
private void addScalarToArray(ArrayNode arrayNode, LayoutItemUI item) {
String dataType = item.getLoutItemDataType();
String defaultValue = item.getLoutItemDefault();
boolean isInteger = isIntegerType(dataType);
boolean isInteger = isIntegerType(dataType, item.getLoutItemDecimal());
if (isNumericType(dataType)) {
try {
@@ -416,23 +416,29 @@ public class ApiSpecManService {
}
}
private boolean isIntegerType(String dataType) {
/** BIGDECIMAL/DECIMAL 은 소수 자릿수가 0 이면 정수로 취급한다. */
private boolean isIntegerType(String dataType, int decimalLen) {
if (dataType == null) return false;
switch (dataType.toUpperCase()) {
case "INTEGER":
case "INT":
case "LONG":
return true;
case "DECIMAL":
case "BIGDECIMAL":
return decimalLen <= 0;
default:
return false;
}
}
private boolean isNumericType(String dataType) {
if (isIntegerType(dataType)) return true;
if (dataType == null) return false;
switch (dataType.toUpperCase()) {
case "INTEGER":
case "INT":
case "LONG":
case "DECIMAL":
case "DOUBLE":
case "BIGDECIMAL":
@@ -621,11 +627,7 @@ public class ApiSpecManService {
parameter.put("required", false); // 기본값으로 false 설정, 필요시 true로 변경 가능
ObjectNode schema = parameter.putObject("schema");
schema.put("type", mapSwaggerType(item.getLoutItemDataType()));
int length = item.getLoutItemLength();
if (length > 0) {
schema.put("maxLength", item.getLoutItemLength());
}
putScalarSchema(schema, item);
}
}
}
@@ -651,11 +653,7 @@ public class ApiSpecManService {
parameter.put("required", false); // 기본값 false, 필요시 사용자가 변경
ObjectNode schema = parameter.putObject("schema");
schema.put("type", mapSwaggerType(item.getLoutItemDataType()));
int length = item.getLoutItemLength();
if (length > 0) {
schema.put("maxLength", length);
}
putScalarSchema(schema, item);
}
}
@@ -665,11 +663,7 @@ public class ApiSpecManService {
ObjectNode header = headers.putObject(item.getLoutItemName());
header.put("description", item.getLoutItemDesc());
ObjectNode schema = header.putObject("schema");
schema.put("type", mapSwaggerType(item.getLoutItemDataType()));
int length = item.getLoutItemLength();
if (length > 0) {
schema.put("maxLength", item.getLoutItemLength());
}
putScalarSchema(schema, item);
}
}
}
@@ -695,14 +689,10 @@ public class ApiSpecManService {
switch (item.getLoutItemType().toUpperCase()) {
case "FIELD":
ObjectNode fieldSchema = parentNode.putObject(item.getLoutItemName());
fieldSchema.put("type", mapSwaggerType(item.getLoutItemDataType()));
putScalarSchema(fieldSchema, item);
if (StringUtils.isNotEmpty(item.getLoutItemDesc())) {
fieldSchema.put("description", item.getLoutItemDesc());
}
int length = item.getLoutItemLength();
if (length > 0) {
fieldSchema.put("maxLength", item.getLoutItemLength());
}
break;
case "GROUP":
@@ -730,14 +720,18 @@ public class ApiSpecManService {
arrItems.put("type", "object");
depthMap.put(item.getLoutItemDepth(), arrItems.putObject("properties"));
} else {
arrItems.put("type", mapSwaggerType(item.getLoutItemDataType()));
putScalarSchema(arrItems, item);
}
break;
}
}
}
private String mapSwaggerType(String dataType) {
/**
* 레이아웃 데이터타입 → OpenAPI type.
* <p>BIGDECIMAL/DECIMAL 은 소수 자릿수(decimalLen)가 0 이면 정수로 취급한다.
*/
private String mapSwaggerType(String dataType, int decimalLen) {
if (dataType == null) return "string";
switch (dataType.toUpperCase()) {
@@ -747,8 +741,9 @@ public class ApiSpecManService {
case "LONG":
return "integer";
case "DECIMAL":
case "DOUBLE":
case "BIGDECIMAL":
return decimalLen > 0 ? "number" : "integer";
case "DOUBLE":
return "number";
case "BOOLEAN":
return "boolean";
@@ -757,6 +752,23 @@ public class ApiSpecManService {
}
}
/**
* 스칼라 FIELD 의 스키마(type/maxLength/소수 자릿수)를 채운다.
* <p>길이(maxLength)는 레이아웃 길이 그대로이며, 소수가 있는 숫자의 예제값은
* 소수점(.)을 포함해 이 길이를 넘지 않도록 프론트에서 생성한다(예: 길이 9/소수 5 → 123.45678).
*/
private void putScalarSchema(ObjectNode schema, LayoutItemUI item) {
String type = mapSwaggerType(item.getLoutItemDataType(), item.getLoutItemDecimal());
schema.put("type", type);
int length = item.getLoutItemLength();
if (length > 0) {
schema.put("maxLength", length);
}
if ("number".equals(type) && item.getLoutItemDecimal() > 0) {
schema.put("x-djb-decimal", item.getLoutItemDecimal());
}
}
private boolean isRequestBodyRequired(String method) {
if (method == null) return false;
return "POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method);
@@ -45,7 +45,7 @@ public class DjbApiSpecLayoutMergeService {
r.put("desc", it.getLoutItemDesc());
r.put("itemType", it.getLoutItemType()); // FIELD / GROUP / GRID
r.put("dataType", dataType);
r.put("category", sampleGenerator.categoryOf(dataType));
r.put("category", sampleGenerator.categoryOf(dataType, dec));
r.put("length", len);
r.put("decimal", dec);
r.put("depth", it.getLoutItemDepth());
@@ -4,19 +4,25 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
* D10 예제값 자동 생성 규칙.
* D10 예제값 자동 생성 규칙. (프론트 app.js 의 _defScalar/_numSample 와 동일 규칙)
* <ul>
* <li>string → {@code sample_} + 항목명. 최대길이 초과 시 left-cut(앞에서 자름).</li>
* <li>integer → {@code 1}</li>
* <li>number → {@code 1.1} (소수 자릿수 지정 시 {@code 1.1000} 형태)</li>
* <li>integer → {@code 123456} (길이 지정 시 그 길이에 맞춰 자릿수 축소)</li>
* <li>number → 소수점(.)을 포함한 전체 길이 기준. 길이 9 / 소수 5 → {@code 123.45678}</li>
* <li>boolean → {@code false}</li>
* </ul>
* <p>BIGDECIMAL/DECIMAL 은 소수 자릿수가 0 이면 정수(integer)로 분류한다.
*/
@Service
public class DjbApiSpecSampleGenerator {
/** 원시 데이터타입(레이아웃 값)을 OpenAPI 계열 카테고리로 분류. */
public String categoryOf(String rawDataType) {
/**
* 원시 데이터타입(레이아웃 값)을 OpenAPI 계열 카테고리로 분류.
*
* @param rawDataType 레이아웃 데이터타입(원시)
* @param decimalLen 소수 자릿수. decimal 계열에서 0 이면 정수로 본다.
*/
public String categoryOf(String rawDataType, int decimalLen) {
if (rawDataType == null) {
return "string";
}
@@ -24,18 +30,22 @@ public class DjbApiSpecSampleGenerator {
if (t.contains("bool")) {
return "boolean";
}
if (t.contains("decimal") || t.contains("double") || t.contains("float") || t.contains("number")) {
return "number";
}
if (t.contains("int") || t.contains("long")) {
return "integer";
}
if (t.contains("object") || t.contains("group")) {
return "object";
}
if (t.contains("array") || t.contains("grid") || t.contains("list")) {
return "array";
}
if (t.contains("decimal")) {
// BigDecimal/Decimal: 소수 자릿수 0 = 정수
return decimalLen > 0 ? "number" : "integer";
}
if (t.contains("double") || t.contains("float") || t.contains("number")) {
return "number";
}
if (t.contains("int") || t.contains("long")) {
return "integer";
}
return "string";
}
@@ -43,22 +53,15 @@ public class DjbApiSpecSampleGenerator {
* 예제값 생성.
* @param rawDataType 레이아웃 데이터타입(원시)
* @param fieldName 항목명
* @param maxLen 최대 길이(0/음수면 무제한)
* @param decimalLen 소수 자릿수(0이면 미적용)
* @param maxLen 최대 길이(0/음수면 무제한). 숫자는 소수점(.)을 포함한 문자열 길이 기준.
* @param decimalLen 소수 자릿수(0이면 정수)
*/
public String exampleFor(String rawDataType, String fieldName, int maxLen, int decimalLen) {
switch (categoryOf(rawDataType)) {
switch (categoryOf(rawDataType, decimalLen)) {
case "integer":
return "1";
return numberSample(maxLen, 0);
case "number":
if (decimalLen > 0) {
StringBuilder sb = new StringBuilder("1.");
for (int i = 0; i < decimalLen; i++) {
sb.append(i == 0 ? '1' : '0');
}
return sb.toString();
}
return "1.1";
return numberSample(maxLen, decimalLen > 0 ? decimalLen : 3);
case "boolean":
return "false";
case "object":
@@ -72,4 +75,37 @@ public class DjbApiSpecSampleGenerator {
return v;
}
}
/**
* 숫자 예제값. 길이는 소수점(.)까지 포함한 문자열 전체 길이로 계산한다.
* <p>예) 길이 9 / 소수 5 → {@code 123.45678} (정수부 3 + '.' 1 + 소수부 5 = 9자).
* 길이가 소수부+2 보다 짧으면 정수부 1자리를 확보하고 소수부를 줄인다.
* 길이 미지정이면 정수부 6자리를 사용한다.
*/
private String numberSample(int maxLen, int decimalLen) {
if (decimalLen <= 0) {
return digits(0, maxLen > 0 ? Math.min(maxLen, 6) : 6);
}
int dec = decimalLen;
int intLen;
if (maxLen > 0) {
intLen = maxLen - dec - 1; // '.' 1자리 포함
if (intLen < 1) {
intLen = 1;
dec = Math.max(1, maxLen - 2);
}
} else {
intLen = 6;
}
return digits(0, intLen) + "." + digits(intLen, dec);
}
/** start 위치부터 1~9 를 순환하는 자릿수 문자열('123456789123...'). */
private String digits(int start, int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append((char) ('1' + ((start + i) % 9)));
}
return sb.toString();
}
}
@@ -322,5 +322,38 @@
VALUES
(#eaiSvcName#, #eaiBzwkDstCd#, #interfaceId#, #interfaceDesc#, #io#, #splizdmndDstCd#, #inExtnlDstCd#, #svcMotivuseDstCd#, #bzwksvckeyName#, #layoutMappingName#, #loginId#, #loginId# )
</statement>
<statement id="selectDetailAPI" parameterClass="java.util.HashMap" resultClass="java.util.HashMap">
SELECT EAISvcName
, EAIBzwkDstcd
FROM $schemaId$.TSEAIHE01
WHERE EAISvcName = #EAISVCNAME#
</statement>
<statement id="insertToHE01" parameterClass="java.util.HashMap">
INSERT INTO $schemaId$.TSEAIHE01
(EAISVCNAME, EAIBZWKDSTCD, SVCHMSEONOT, SVCMOTIVUSEDSTCD, SVCPRCESDSTICNAME, FLOWCTRLROUTNAME, INTGRADSTICNAME,
SVCBFCLMNLOGYN, GSTATSYSADPTRBZWKGROUPNAME, SEVRLOGLVELNO, SVCLOGLVELNO, EAISVCDESC, EAILASTAMNDYMS, TRANTYPE, USEYN, AUTHOR, AUTHTYPE, APIENABLEDYN)
VALUES
(#EAISVCNAME#, #EAIBZWKDSTCD#, '0', 'SYNC', 'SINGLE', 'BASEFC', 'ONLINE',
'0', #GSTATSYSADPTRBZWKGROUPNAME#, 0, 0, #EAISVCDESC#, TO_CHAR(SYSTIMESTAMP, 'YYYYMMDDHH24MISSFF3'), 'R', '1', 'IIM', 'none', 'N')
</statement>
<statement id="insertToHE02" parameterClass="java.util.HashMap">
INSERT INTO $schemaId$.TSEAIHE02
(EAISVCNAME, SVCPRCSSNO, PSVINTFACDSTICNAME, PSVSYSBZWKDSTCD, PSVSYSIDNAME, PSVSYSADPTRBZWKGROUPNAME, FLOVRYN, CHNGEONOT,
BASCRSPNSCHNGEONOT, ERRRSPNSCHNGEONOT, NEXTSVCPRCSSNO, OUTBNDROUTNAME, TOUTVAL, SUPPLDELYN, HDRCTRLDSTCD, RESTOPTION)
VALUES
(#EAISVCNAME#, 1, 'SYNC', #EAIBZWKDSTCD#, 'EAIDOM', #PSVSYSADPTRBZWKGROUPNAME#, 0, 0,
0, 0, 1, #OUTBNDROUTNAME#, 30, '0', '01', #RESTOPTION#)
</statement>
<statement id="deleteToHE02" parameterClass="java.util.HashMap">
DELETE FROM $schemaId$.TSEAIHE02
WHERE EAISVCNAME = #EAISVCNAME#
</statement>
</sqlMap>
@@ -453,6 +453,6 @@
INSERT INTO $schemaId$.TSEAITR10
( LOGPRCSSSEQNO, SEQNO, SERVICENAME ,COMMAND,LOUTNAME,RECVDATA,RECVAMNDHMS,PRCSSRSLT, PRCSSRSLTCMNT )
VALUES
( #logPrcssSeqno#, #seqno#, #serviceName# ,#command#,#loutName#,#recvData#,#recvAmndHMS#,#prcssRslt#, #prcssRsltCmnt# )
( #logPrcssSeqno#, $schemaId$.TSEAITR10_SEQNO.NEXTVAL, #serviceName# ,#command#,#loutName#,#recvData#,#recvAmndHMS#,#prcssRslt#, #prcssRsltCmnt# )
</statement>
</sqlMap>
+1 -1
View File
@@ -4,7 +4,7 @@
<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="/logs/eapim/${inst.Name:-emsSvr00}" />
<property name="LOG_HOME" value="${logging.log-path:-/logs/eapim}/${inst.Name:-emsSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
+1 -1
View File
@@ -4,7 +4,7 @@
<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="D:/Log/App/eapim/${inst.Name:-devSvr00}" />
<property name="LOG_HOME" value="${logging.log-path:-D:/Log/eapim}/${inst.Name:-devSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
+1 -1
View File
@@ -4,7 +4,7 @@
<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="/logs/eapim/${inst.Name:-emsSvr00}" />
<property name="LOG_HOME" value="${logging.log-path:-/logs/prod/eapim}/${inst.Name:-emsSvr00}" />
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" />
@@ -723,6 +723,7 @@ eaiMessage.failSts = Failover Status
eaiMessage.failoverWhether = Failover whether or not
eaiMessage.flowCtrlRoutName = FlowControl Routing Name
eaiMessage.gstaSysAdptBzwkGroupName = Activation interface type
eaiMessage.gstatSysAdptr = Activation Adapter
eaiMessage.hdrConClaCd = Header Control Classification Code
eaiMessage.ifIdKeyVal = IF identification key value
eaiMessage.ifSvcMsgList = Interface Service Message List
@@ -739,6 +740,7 @@ eaiMessage.nextSvcPrcOrd = Next Service Process Order
eaiMessage.nextSvcSeq = Next service process sequence
eaiMessage.outRoutNm = Outbound Routing Name
eaiMessage.proOrder = Processing order
eaiMessage.psvSysAdptr = Passive Adapter
eaiMessage.psvintfacdsticName = Passive Interface Type
eaiMessage.queueName = Queue Name
eaiMessage.refClsNm = Reference Class Name
@@ -852,6 +852,7 @@ eaiMessage.failSts = Failover ?
eaiMessage.failoverWhether = Failover ?
eaiMessage.flowCtrlRoutName = FlowControl Routing
eaiMessage.gstaSysAdptBzwkGroupName = 1st Adapter
eaiMessage.gstatSysAdptr = 1st Adapter
eaiMessage.hdrConClaCd = Header Control Division
eaiMessage.ifIdKeyVal = IF Identification key value
eaiMessage.ifSvcMsgList = Interface Service Message List
@@ -888,6 +888,7 @@ eaiMessage.failSts = FailOver\uC5EC\uBD80
eaiMessage.failoverWhether = failover\uC5EC\uBD80
eaiMessage.flowCtrlRoutName = FlowControl
eaiMessage.gstaSysAdptBzwkGroupName = \uAE30\uB3D9\uC778\uD130\uD398\uC774\uC2A4 \uC720\uD615
eaiMessage.gstatSysAdptr = \uAE30\uB3D9\uC5B4\uB311\uD130
eaiMessage.hdrConClaCd = \uD5E4\uB354\uC81C\uC5B4\uAD6C\uBD84\uCF54\uB4DC
eaiMessage.ifIdKeyVal = EAI \uC2DD\uBCC4\uD0A4\uAC12
eaiMessage.ifSvcMsgList = OUTBOUND \uC11C\uBE44\uC2A4 \uBA54\uC2DC\uC9C0 \uBAA9\uB85D
@@ -904,6 +905,7 @@ eaiMessage.nextSvcPrcOrd = \uB2E4\uC74C\uC11C\uBE44\uC2A4\uCC98\uB9AC
eaiMessage.nextSvcSeq = \uB2E4\uC74C\uC11C\uBE44\uC2A4\uCC98\uB9AC\uC21C\uC11C
eaiMessage.outRoutNm = Outbound\uB77C\uC6B0\uD305\uBA85
eaiMessage.proOrder = \uCC98\uB9AC\uC21C\uC11C
eaiMessage.psvSysAdptr = \uC218\uB3D9\uC5B4\uB311\uD130
eaiMessage.psvintfacdsticName = \uC218\uB3D9\uC778\uD130\uD398\uC774\uC2A4 \uC720\uD615
eaiMessage.queueName = \uD050\uBA85
eaiMessage.refClsNm = \uCC38\uACE0\uD074\uB798\uC2A4\uBA85