Files
rtgs/frontend/public/manual.html
rtgs c96664e52d feat(frontend): 포탈·운영·관리자 콘솔 및 매뉴얼·거래 타임라인·전문탭·서비스 제어 UI
- 콘솔을 포탈/운영/관리자 3뷰로 분리(Portal·AdminPanel·AdminLogin·auth·util), ADMIN JWT 로그인 게이트
- 상단 우측 '서비스 매뉴얼'(public/manual.html) 링크, 로그인 화면 불필요 계좌조회 에러 제거
- 거래 조회: 서비스별 처리시각 5단계 타임라인 + pacs.008/pacs.002 전문 탭(/notifications 프록시)
- 관리자: 서비스 제어 패널(기동/중지/재기동/로그, 4초 폴링, 인프라 상태 포함)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:41:35 +09:00

335 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RTGS 포탈 · 서비스 매뉴얼</title>
<style>
:root {
--gucci: #6b4fbb; --chanel: #0b63c4; --hermes: #c77d0a;
--dior: #8a4fd0; --prada: #b8860b; --lv: #1a7f37; --seq: #444;
--ink: #222; --muted: #666; --line: #e2e2e2; --bg: #f4f7fb;
}
* { box-sizing: border-box; }
body {
font-family: system-ui, "Segoe UI", "Malgun Gothic", sans-serif;
color: var(--ink); line-height: 1.65; margin: 0;
background: #fafbfc;
}
.wrap { max-width: 960px; margin: 0 auto; padding: 32px 24px 80px; }
header.doc {
background: linear-gradient(135deg, #1a2a44, #2c4468);
color: #fff; border-radius: 12px; padding: 28px 30px; margin-bottom: 8px;
}
header.doc h1 { margin: 0 0 6px; font-size: 26px; }
header.doc p { margin: 0; color: #cdd8ea; font-size: 14px; }
.toc {
background: #fff; border: 1px solid var(--line); border-radius: 10px;
padding: 16px 22px; margin: 20px 0 8px; font-size: 14px;
}
.toc b { display: block; margin-bottom: 8px; color: var(--muted); font-size: 12px; letter-spacing: .04em; text-transform: uppercase; }
.toc a { color: var(--chanel); text-decoration: none; margin-right: 16px; white-space: nowrap; line-height: 2; }
.toc a:hover { text-decoration: underline; }
h2 {
font-size: 20px; margin: 40px 0 14px; padding-bottom: 8px;
border-bottom: 2px solid #eceff3;
}
h3 { font-size: 16px; margin: 22px 0 8px; }
p, li { font-size: 15px; }
code {
background: #eef1f5; padding: 1px 6px; border-radius: 4px;
font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 13px;
}
.lead { color: var(--muted); font-size: 15px; }
/* 상태 범례 */
.legend { display: flex; gap: 20px; flex-wrap: wrap; background: var(--bg); border-radius: 10px; padding: 14px 18px; margin: 12px 0; }
.legend div { font-size: 14px; }
/* 흐름도 */
.flow { display: flex; align-items: stretch; gap: 4px; flex-wrap: wrap; margin: 14px 0; }
.flow .node {
flex: 1; min-width: 92px; border: 2px solid #ccd3dd; border-radius: 8px;
background: #fff; text-align: center; padding: 10px 6px; font-size: 13px;
}
.flow .node small { display: block; color: var(--muted); font-size: 11px; margin-top: 2px; }
.flow .arrow { align-self: center; color: #99a; font-size: 18px; flex: 0 0 auto; }
/* 서비스 카드 */
.svc {
border: 1px solid var(--line); border-top: 5px solid var(--bar, #888);
border-radius: 10px; padding: 20px 22px; margin: 18px 0;
background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.04);
}
.svc h3 { margin-top: 0; font-size: 18px; display: flex; align-items: center; gap: 8px; }
.svc .tag {
font-size: 11px; font-weight: 600; color: #fff; background: var(--bar, #888);
padding: 2px 9px; border-radius: 20px; margin-left: auto;
}
.svc .sub { color: var(--muted); font-size: 13px; margin: -4px 0 12px; }
.meta-row { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 13px; color: #555; margin-bottom: 10px; }
.meta-row span b { color: var(--ink); }
table { border-collapse: collapse; width: 100%; margin: 10px 0; font-size: 14px; }
th, td { border: 1px solid #e6e9ee; padding: 7px 11px; text-align: left; vertical-align: top; }
th { background: #f4f7fb; font-weight: 600; }
td code { white-space: nowrap; }
.metrics th:first-child, .metrics td:first-child { width: 34%; }
.note { background: #fffbe9; border: 1px solid #f0e2a8; border-radius: 8px; padding: 12px 16px; font-size: 14px; margin: 14px 0; }
.btn-chip { display: inline-block; border: 1px solid #ccc; border-radius: 6px; padding: 2px 10px; font-size: 13px; background: #fff; margin: 2px 4px 2px 0; }
footer { margin-top: 50px; padding-top: 18px; border-top: 1px solid var(--line); color: #999; font-size: 13px; }
</style>
</head>
<body>
<div class="wrap">
<header class="doc">
<h1>🏛 RTGS 포탈 서비스 매뉴얼</h1>
<p>소액 RTGS(실시간총액결제) 프로토타입 · 7개 서비스 안내 · Active-Active-Active 복원력 설계</p>
</header>
<nav class="toc">
<b>목차</b>
<a href="#intro">1. 포탈 개요</a>
<a href="#read">2. 화면 읽는 법</a>
<a href="#flow">3. 업무 흐름</a>
<a href="#services">4. 서비스별 설명</a>
<a href="#status">5. 거래 상태(TxSts)</a>
<a href="#ports">6. 포트 일람</a>
<a href="#glossary">7. 용어</a>
</nav>
<h2 id="intro">1. 포탈 개요</h2>
<p class="lead">
본 포탈은 소액 <b>RTGS(Real-Time Gross Settlement, 실시간총액결제)</b> 프로토타입의 운영·관리 콘솔입니다.
거래는 <b>단일 전역 순번기</b>가 입구에서 순번을 부여하고, 각 센터가 그 순번대로 동일하게 재생(결정론적 처리)하며,
<b>원장(PostgreSQL)</b>이 잔액을 강한 일관성으로 확정합니다. 3센터 <b>Active-Active-Active</b> 구성에서
정족수(2/3)와 펜싱으로 split-brain을 차단하는 것이 설계의 핵심입니다.
</p>
<p>
시스템은 명품 브랜드를 코드네임으로 쓰는 <b>7개 서비스</b>로 구성됩니다. 포탈 화면에는 이 중
주요 6개가 카드로 표시되고, 전역 순번기(Sequencer)는 업무 흐름도의 진입 노드로 나타납니다.
각 서비스가 무슨 일을 하고, 카드에 표시되는 숫자가 무엇을 뜻하는지 이 문서에서 설명합니다.
</p>
<h2 id="read">2. 화면 읽는 법</h2>
<h3>상태 표시등</h3>
<div class="legend">
<div>🟢 <b>UP</b> — 정상 가동(최근 하트비트 수신)</div>
<div>🟡 <b>STALE</b> — 하트비트 지연(응답이 오래됨, 점검 필요)</div>
<div>🔴 <b>DOWN</b> — 중단(하트비트 없음/도달 불가)</div>
</div>
<p>
상단 바의 <code>● n/7 UP</code>은 7개 서비스 중 정상 가동 중인 수를 나타냅니다.
각 서비스는 DB의 <code>service_heartbeat</code>에 주기적으로 살아있음을 기록하고(헤드리스 포함),
관리 서비스가 이를 읽어 UP/STALE/DOWN을 판정합니다.
</p>
<h3>자동 갱신 · 새로고침</h3>
<p>
<span class="btn-chip">자동갱신 5s</span> 체크 시 5초마다 상태·지표를 다시 불러옵니다.
<span class="btn-chip">새로고침</span>으로 즉시 갱신할 수도 있습니다.
카드의 처리 건수는 <b>DB 집계(원장)</b> 기준이라 관리자 콘솔에서 초기화하면 자연히 0으로 돌아갑니다.
</p>
<h2 id="flow">3. 업무 흐름</h2>
<p>한 건의 이체는 아래 순서로 처리됩니다. 각 노드는 담당 서비스이며, 포탈 흐름도에서 색 테두리로 실시간 상태를 함께 보여줍니다.</p>
<div class="flow">
<div class="node">🔢 Sequencer<small>전역순번</small></div>
<div class="arrow"></div>
<div class="node">🛡️ Gucci<small>관문 인증</small></div>
<div class="arrow"></div>
<div class="node">📥 Chanel<small>접수</small></div>
<div class="arrow"></div>
<div class="node">🔄 Dior<small>동기화</small></div>
<div class="arrow"></div>
<div class="node">💰 Hermes<small>결제</small></div>
<div class="arrow"></div>
<div class="node">✅ Prada<small>처리동기화</small></div>
<div class="arrow"></div>
<div class="node">📥 Chanel<small>결과통보</small></div>
</div>
<p class="lead">
전역순번(Sequencer) 부여 → 관문 인증(Gucci) → 접수(Chanel) → 저널/센터 동기화(Dior) →
결제·원장(Hermes) → 처리 동기화·완결(Prada) → 결과통보(Chanel).
</p>
<h2 id="services">4. 서비스별 설명</h2>
<!-- Sequencer -->
<div class="svc" style="--bar: var(--seq)">
<h3>🔢 Sequencer <span class="tag">전역 순번기 · Headless</span></h3>
<div class="sub">전역순번 부여 · 결정론적 값 확정 → 저널 발행</div>
<div class="meta-row"><span>논리포트 <b>8090</b></span><span>유형 <b>Headless JVM</b></span><span>Kafka: <code>rtgs.inbound</code><code>rtgs.journal</code></span></div>
<p>
시스템의 <b>단일 진입 순서 결정자</b>입니다. 접수된 요청에 전역순번(<code>global_seq</code>)을 부여해
단일 파티션 저널에 발행하면, 모든 센터가 이 순서를 그대로 재생해 동일한 결과를 만듭니다(결정론적 처리).
재기동 시 <code>MAX(global_seq)</code>에서 이어서 발번하므로 순번 유실이 없습니다.
</p>
<p class="lead">※ 순서 결정만 담당(상태 O). 분산·라우팅은 Gucci/GSLB(상태 X)가 맡아 역할을 분리합니다.
포탈에서는 별도 카드 없이 흐름도의 진입 노드로 표시됩니다.</p>
</div>
<!-- Gucci -->
<div class="svc" style="--bar: var(--gucci)">
<h3>🛡️ Gucci <span class="tag">게이트웨이 · 기관 인증</span></h3>
<div class="sub">외부 경계 관문 — 로그인 · JWT · API인증 · 유량제어 · 재전송차단 · 결과 콜백송부</div>
<div class="meta-row"><span>논리포트 <b>8095</b></span><span>유형 <b>Web(Tomcat)</b></span><span>코드네임 <b>Global User Communication Control Interface</b></span></div>
<p>
참가기관과 시스템 사이의 <b>대문</b>입니다. 로그인 후 JWT(HS256)를 발급하고, 모든 요청의 인증·인가
(역할 + 기관 바인딩)를 검사합니다. 기관별 유량제어로 코어·순번기를 보호하고(백프레셔),
nonce+timestamp로 재전송을 차단합니다. 처리 결과는 기관 콜백 URL로 재시도·ACK 방식으로 송부합니다.
</p>
<table class="metrics">
<tr><th>카드 지표</th><th>의미</th></tr>
<tr><td>코어(샤넬)</td><td>Gucci가 바라본 코어(Chanel) 연결 상태</td></tr>
<tr><td>등록기관</td><td>당좌계좌가 등록된 참가기관 수</td></tr>
</table>
<div>바로가기 <span class="btn-chip">감사로그(Kibana)</span> — 인증·유량·재전송 등 감사 이벤트를 Kibana에서 조회</div>
</div>
<!-- Chanel -->
<div class="svc" style="--bar: var(--chanel)">
<h3>📥 Chanel <span class="tag">접수</span></h3>
<div class="sub">pacs.008 접수 · XSD검증 · 경량화 · 원전문 저장 · 결과통보(pacs.002)</div>
<div class="meta-row"><span>논리포트 <b>8091</b></span><span>유형 <b>Web(Tomcat)</b></span><span>코드네임 <b>Contact Hub · Entry Liaison</b></span></div>
<p>
참가기관의 결제의뢰 전문(<b>ISO 20022 pacs.008</b>)을 받는 <b>접수 창구</b>입니다.
공식 XSD로 검증(XXE 차단)하고 원전문을 SHA-256 해시와 함께 저장한 뒤, 경량 명령문으로 만들어
입구 토픽에 발행합니다. 처리가 끝나면 <b>pacs.002</b> 결과통보 전문을 생성해 아웃박스에 적재합니다.
</p>
<table class="metrics">
<tr><th>카드 지표</th><th>의미</th></tr>
<tr><td>누적 접수</td><td>지금까지 접수된 거래 건수(원장 집계)</td></tr>
<tr><td>접수율</td><td>초당 접수 건수(TPS, 최근 갱신 구간 기준)</td></tr>
</table>
<div>바로가기 <span class="btn-chip">이체 신청/조회</span> — 운영 콘솔에서 pacs.008 신청 및 거래 상태 조회</div>
</div>
<!-- Dior -->
<div class="svc" style="--bar: var(--dior)">
<h3>🔄 Dior <span class="tag">센터간 접수 동기화</span></h3>
<div class="sub">저널 소비 → 원장 PDNG 기록(접수 동기화)</div>
<div class="meta-row"><span>논리포트 <b>8092</b></span><span>유형 <b>Headless JVM</b></span><span>코드네임 <b>Data Integration · Relay</b></span></div>
<p>
전역순번 저널을 소비해 원장에 거래를 <b>PDNG(대기)</b> 상태로 기록합니다. 결제(Hermes)와 병행해
저널을 읽으면서 <b>모든 센터의 접수 상태를 동일하게 맞추는</b> 동기화 역할을 합니다.
</p>
<table class="metrics">
<tr><th>카드 지표</th><th>의미</th></tr>
<tr><td>메모리</td><td>프로세스 사용 메모리(MB) — 런타임 자원 지표</td></tr>
<tr><td>가동</td><td>프로세스 가동 시간(초)</td></tr>
</table>
<div>바로가기 <span class="btn-chip">원장 보기</span> — 관리자 콘솔의 원장/거래 현황</div>
</div>
<!-- Hermes -->
<div class="svc" style="--bar: var(--hermes)">
<h3>💰 Hermes <span class="tag">결제 · 원장 엔진</span></h3>
<div class="sub">저널 순차소비 · 순서교정 · 선(先)저널 · 당좌 차/대변(강한 일관성) · ACSP</div>
<div class="meta-row"><span>논리포트 <b>8093</b></span><span>유형 <b>Headless JVM</b></span><span>코드네임 <b>Rapid Memory Execution · Settlement</b></span></div>
<p>
실제 <b>잔액을 움직이는 결제 엔진</b>입니다. 저널을 순번대로(동시성 1) 소비하며 순서를 교정하고,
잔액 연산 전에 <b>선저널(write-ahead)</b>로 순번을 확정한 뒤 송신·수신 기관 당좌계좌를 차/대변 처리합니다
(PostgreSQL 강한 일관성 트랜잭션 → 이중지급 0·총액 보존). 정산이 반영되면 <b>ACSP</b> 결과를 발행합니다.
선저널 덕분에 엔진이 죽었다 살아나도 Kafka 오프셋 재생으로 무손실 승계됩니다.
</p>
<table class="metrics">
<tr><th>카드 지표</th><th>의미</th></tr>
<tr><td>결제완료(ACCC)</td><td>최종 완결된 거래 건수(원장 집계)</td></tr>
<tr><td>평균 처리</td><td>정산 1건당 평균 소요시간(ms) — 런타임 지표</td></tr>
<tr><td>미결(대기)</td><td>접수됐으나 아직 완결·반려되지 않은 건수(전체 ACCC RJCT)</td></tr>
</table>
<div>바로가기 <span class="btn-chip">원장 보기</span> — 관리자 콘솔의 원장/잔액</div>
</div>
<!-- Prada -->
<div class="svc" style="--bar: var(--prada)">
<h3>✅ Prada <span class="tag">센터간 처리 동기화 · 완결</span></h3>
<div class="sub">결과 소비 · 완결 규율(과반) · ACCC 확정 · 조회사본 · 결과통보 발행</div>
<div class="meta-row"><span>논리포트 <b>8094</b></span><span>유형 <b>Headless JVM</b></span><span>코드네임 <b>Persistent Repository · Aggregation</b></span></div>
<p>
정산 결과를 소비해 <b>완결 규율</b>을 적용합니다. 과반(정족수) 센터가 동일 결과를 확정하면
최종 상태 <b>ACCC(입금처리완료)</b>로 확정하고 조회 전용 사본을 남깁니다. 완결이 커밋되면
결과통보(<code>rtgs.notify</code>)를 발행해 Chanel→Gucci를 통한 기관 통보로 이어집니다.
</p>
<table class="metrics">
<tr><th>카드 지표</th><th>의미</th></tr>
<tr><td>완결(ACCC)</td><td>최종 완결된 거래 건수</td></tr>
<tr><td>반려(RJCT)</td><td>검증 실패·잔액 부족 등으로 반려된 건수</td></tr>
</table>
<div>바로가기 <span class="btn-chip">완결/조회사본</span> — 관리자 콘솔의 완결 현황</div>
</div>
<!-- Louis Vuitton -->
<div class="svc" style="--bar: var(--lv)">
<h3>🛠️ Louis Vuitton <span class="tag">관리자 · 운영</span></h3>
<div class="sub">시스템관리 — DB초기화 · 코드 · 사용자권한 · 대사 대시보드</div>
<div class="meta-row"><span>논리포트 <b>8099</b></span><span>유형 <b>Web(Tomcat)</b></span><span>코드네임 <b>Leading Operations Unified Info Systems</b></span></div>
<p>
시스템 <b>관리·운영</b> 서비스입니다. DB 초기화, 상태코드·기관·사용자 권한 관리, 헬스(하트비트) 집계,
원장=저널=조회사본 <b>대사(對査) 대시보드</b>를 제공합니다. 스키마는 Flyway로 형상 관리하며 기동 시 자동 적용합니다.
(관리자 콘솔은 ADMIN JWT 필수, 테스트 계정 <code>a / 1</code>)
</p>
<table class="metrics">
<tr><th>카드 지표</th><th>의미</th></tr>
<tr><td>총 거래</td><td>원장에 기록된 전체 거래 건수</td></tr>
<tr><td>최대순번</td><td>부여된 전역순번의 최댓값(<code>MAX(global_seq)</code>)</td></tr>
</table>
<div>바로가기 <span class="btn-chip">관리자 콘솔</span> <span class="btn-chip">로그(Kibana)</span></div>
</div>
<h2 id="status">5. 거래 상태(TxSts) 전이</h2>
<p>한 건의 거래는 아래 상태를 따라 진행됩니다.</p>
<table>
<tr><th>상태</th><th>한글</th><th>담당</th><th>설명</th></tr>
<tr><td><code>RCVD</code></td><td>접수</td><td>Chanel</td><td>전문 접수·검증 통과</td></tr>
<tr><td><code>ACTC</code></td><td>순번</td><td>Sequencer</td><td>전역순번 부여</td></tr>
<tr><td><code>PDNG</code></td><td>대기</td><td>Dior</td><td>원장에 대기 기록(접수 동기화)</td></tr>
<tr><td><code>ACSP</code></td><td>정산반영</td><td>Hermes</td><td>당좌 차/대변 반영</td></tr>
<tr><td><code>ACCC</code></td><td>입금처리완료</td><td>Prada</td><td>과반 확정 · 최종 완결</td></tr>
<tr><td><code>RJCT</code></td><td>반려</td><td>Chanel/Hermes</td><td>XSD·업무규칙 실패 또는 잔액부족·미등록기관</td></tr>
</table>
<div class="note">
정상 흐름: <code>RCVD → ACTC → PDNG → ACSP → ACCC</code>.
검증·잔액 문제 시 <code>RJCT</code>로 종료됩니다.
</div>
<h2 id="ports">6. 포트 일람</h2>
<table>
<tr><th>서비스</th><th>포트</th><th>유형</th></tr>
<tr><td>🔢 Sequencer</td><td><code>8090</code></td><td>Headless JVM (논리)</td></tr>
<tr><td>📥 Chanel</td><td><code>8091</code></td><td>Web (Tomcat)</td></tr>
<tr><td>🔄 Dior</td><td><code>8092</code></td><td>Headless JVM (논리)</td></tr>
<tr><td>💰 Hermes</td><td><code>8093</code></td><td>Headless JVM (논리)</td></tr>
<tr><td>✅ Prada</td><td><code>8094</code></td><td>Headless JVM (논리)</td></tr>
<tr><td>🛡️ Gucci</td><td><code>8095</code></td><td>Web (Tomcat)</td></tr>
<tr><td>🛠️ Louis Vuitton</td><td><code>8099</code></td><td>Web (Tomcat)</td></tr>
<tr><td>운영/관리 콘솔 (Frontend)</td><td><code>5174</code></td><td>React + Vite</td></tr>
<tr><td>PostgreSQL (원장)</td><td><code>5433</code></td><td>RDBMS</td></tr>
<tr><td>Kafka (입구/저널/결과/통보)</td><td><code>9092</code></td><td>KRaft</td></tr>
<tr><td>Elasticsearch / Logstash / Kibana</td><td><code>9200 / 5000 / 5601</code></td><td>ELK 로그</td></tr>
</table>
<h2 id="glossary">7. 용어</h2>
<table>
<tr><th>약어</th><th>의미</th></tr>
<tr><td>RTGS</td><td>Real-Time Gross Settlement — 실시간총액결제</td></tr>
<tr><td>A-A-A</td><td>Active-Active-Active — 3센터 동시 가동</td></tr>
<tr><td>BMI</td><td>Business Message Identifier — 거래식별자 22자리(영업일8+기관4+일련10)</td></tr>
<tr><td>저널</td><td>전역순번이 부여된 처리 명단(Kafka 단일 파티션)</td></tr>
<tr><td>정족수</td><td>Quorum(N/2+1). 3센터=2</td></tr>
<tr><td>선저널</td><td>Write-ahead journal — 잔액연산 전 순번 확정(무손실 승계)</td></tr>
<tr><td>pacs.008 / pacs.002</td><td>ISO 20022 결제의뢰 / 결과통보 전문</td></tr>
<tr><td>TPS</td><td>Transactions Per Second — 초당 처리 건수</td></tr>
</table>
<footer>
RTGS 프로토타입 · 운영/관리 콘솔 서비스 매뉴얼 · 작성 루비(AI) · 검토 양희정 팀장(한국은행 RTGS시스템팀)<br>
상세 설계는 <code>docs/RTGS 아키텍처 설계서.md</code> 및 워크플로우 문서를 참조하세요.
</footer>
</div>
</body>
</html>