규모확장 Wave B: 전 11모듈 svc 75→133·batch 35→46 (BAIS 온라인 1:1)

- svc 825→1,463 (모듈당 133), batch 385→506 (모듈당 46) — 전부 새 고유 실업무 로직
  + 카피북 헤더, 디스패처 133 advertise, 한글 기능설명(매니페스트 추출)
- BAIS As-Is 대비: 온라인 1,463 vs 1,466(99.8%), 배치 506 vs 503(초과)
- 화면 매니페스트 1,463 재생성
- 검증: build DONE modules=11 batches=506, 12서버 runok, xadmin psc 1,466 AVAIL,
  매입체인 XA 커밋, 신규서비스 실데이터 스모크, 전 코퍼스 .pgc 클론 0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo-choi 2026-07-20 22:55:47 +00:00
parent ea90bef264
commit c62d91b7a7
1526 changed files with 59213 additions and 12 deletions

View file

@ -0,0 +1,52 @@
/*
* mg_chanfail_batch.pgc - mg 이중화 채널 다운 탐지 배치 (커서 + XA).
*
* 등록된 전 채널의 상태를 커서로 훑어 UP/DOWN 개수를 세고, DOWN 채널이 하나라도
* 있으면 경보 로그를 남긴다 (채널 상태 모니터/이중화 감시의 일배치판).
* Usage: mg_chanfail_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include <userlog.h>
#include "mg_chanfail_batch.h"
int main(int argc, char **argv)
{
(void)argc; (void)argv;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_status[16];
EXEC SQL END DECLARE SECTION;
mg_chanfail_rec_t rec;
memset(&rec, 0, sizeof(rec));
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE cfc CURSOR FOR
SELECT channel, status FROM mg_channel ORDER BY channel;
EXEC SQL OPEN cfc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH cfc INTO :h_chan, :h_status;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cfc; tpabort(0); return 1; }
rec.channels++;
if (strcmp(h_status, "UP") == 0) {
rec.up_cnt++;
} else {
rec.down_cnt++;
userlog("mg_chanfail_batch 채널 DOWN 감지: %s (%s)", h_chan, h_status);
}
}
EXEC SQL CLOSE cfc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_chanfail_batch COMMIT: 채널수=%ld UP=%ld DOWN=%ld\n",
rec.channels, rec.up_cnt, rec.down_cnt);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,58 @@
/*
* mg_dupmark_batch.pgc - mg 중복 STAN 전문 마킹 배치 (커서 + XA).
*
* 당일 STAN 별 건수를 group by 커서로 훑어 2건 이상인 STAN 마다, 최소 msg_id(원본)만
* 남기고 나머지를 rc=94/status=X 로 폐기 마킹한다 (중복검출의 대량 마킹 일배치판).
* Usage: mg_dupmark_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_dupmark_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_stan, h_cnt, h_keep;
EXEC SQL END DECLARE SECTION;
mg_dupmark_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE dmc CURSOR FOR
SELECT stan, count(*) FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY stan HAVING count(*) > 1 ORDER BY stan;
EXEC SQL OPEN dmc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH dmc INTO :h_stan, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dmc; tpabort(0); return 1; }
rec.dup_stans++;
EXEC SQL SELECT min(msg_id) INTO :h_keep
FROM mg_msg_log WHERE biz_date = :h_bizdate AND stan = :h_stan;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dmc; tpabort(0); return 1; }
EXEC SQL UPDATE mg_msg_log SET rc = '94', status = 'X'
WHERE biz_date = :h_bizdate AND stan = :h_stan AND msg_id <> :h_keep;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dmc; tpabort(0); return 1; }
rec.marked += (long)sqlca.sqlerrd[2];
}
EXEC SQL CLOSE dmc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_dupmark_batch COMMIT: bizdate=%s 중복STAN=%ld 폐기마킹=%ld건\n",
bizdate, rec.dup_stans, rec.marked);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,51 @@
/*
* mg_errescal_batch.pgc - mg 재시도초과 오류전문 에스컬레이션 배치 (XA 단일 갱신).
*
* mg_seq(ESCALLIM) 재시도 임계를 읽어, 그 횟수를 넘긴 실패(F) 큐 건을 한 문장에
* 에스컬레이션(E)으로 전이시킨다 (오류전문 재처리 워크플로의 일배치 대량판).
* Usage: mg_errescal_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_errescal_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_lim, h_scanned;
EXEC SQL END DECLARE SECTION;
mg_errescal_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
h_lim = 5;
EXEC SQL SELECT seq_val INTO :h_lim FROM mg_seq WHERE seq_name = 'ESCALLIM';
if (sqlca.sqlcode < 0) { fprintf(stderr, "ESCALLIM 조회 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
if (h_lim <= 0) h_lim = 5;
EXEC SQL SELECT count(*) INTO :h_scanned
FROM mg_queue WHERE status = 'F' AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
rec.scanned = h_scanned;
EXEC SQL UPDATE mg_queue SET status = 'E', updated_at = now()
WHERE status = 'F' AND biz_date = :h_bizdate AND retry_cnt > :h_lim;
if (sqlca.sqlcode < 0) { fprintf(stderr, "에스컬 전이 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
rec.escalated = (long)sqlca.sqlerrd[2];
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_errescal_batch COMMIT: bizdate=%s 실패건=%ld 에스컬=%ld건 (임계=%ld)\n",
bizdate, rec.scanned, rec.escalated, h_lim);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,52 @@
/*
* mg_healthz_batch.pgc - mg 게이트웨이 종합 헬스 스냅 배치 (다중 집계 + XA).
*
* UP 채널 수, 대기(Q) 큐 적체, 당일 오류(rc<>'00') 전문 수를 각각 집계해 한 스냅으로
* 모아, UP 채널이 있고 적체가 임계 미만이면 healthy=1 로 판정한다 (헬스체크 세분).
* Usage: mg_healthz_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_healthz_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
const long backlog_limit = 1000;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_up, h_backlog, h_err;
EXEC SQL END DECLARE SECTION;
mg_healthz_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL SELECT count(*) INTO :h_up FROM mg_channel WHERE status = 'UP';
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
EXEC SQL SELECT count(*) INTO :h_backlog
FROM mg_queue WHERE status = 'Q' AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
EXEC SQL SELECT count(*) INTO :h_err
FROM mg_msg_log WHERE biz_date = :h_bizdate AND rc <> '00';
if (sqlca.sqlcode < 0) { tpabort(0); return 1; }
rec.chan_up = h_up;
rec.queue_backlog = h_backlog;
rec.err_today = h_err;
rec.healthy = (h_up > 0 && h_backlog < backlog_limit) ? 1 : 0;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_healthz_batch COMMIT: bizdate=%s UP채널=%ld 큐적체=%ld 오류전문=%ld 건강=%s\n",
bizdate, rec.chan_up, rec.queue_backlog, rec.err_today,
rec.healthy ? "YES" : "NO");
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,59 @@
/*
* mg_loadrpt_batch.pgc - mg 채널 부하 가중 리포트 배치 (커서 + XA).
*
* 채널별 당일 전문건수와 큐 재시도합을 결합한 부하지표(건수 + 재시도*2)를 커서로
* 훑으며 총 부하와 최대 부하 채널을 집계한다 (트래픽 부하분산 근거용 일배치).
* Usage: mg_loadrpt_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_loadrpt_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long load;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_chan[16];
long h_msgcnt, h_retry;
EXEC SQL END DECLARE SECTION;
mg_loadrpt_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE lrc CURSOR FOR
SELECT channel, count(*) FROM mg_msg_log
WHERE biz_date = :h_bizdate GROUP BY channel ORDER BY channel;
EXEC SQL OPEN lrc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH lrc INTO :h_chan, :h_msgcnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lrc; tpabort(0); return 1; }
rec.channels++;
h_retry = 0;
EXEC SQL SELECT coalesce(sum(retry_cnt),0) INTO :h_retry
FROM mg_queue WHERE biz_date = :h_bizdate AND channel = :h_chan;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lrc; tpabort(0); return 1; }
load = h_msgcnt + h_retry * 2; /* 재시도에 가중 2배 */
rec.load_sum += load;
if (load > rec.busiest_load) rec.busiest_load = load;
}
EXEC SQL CLOSE lrc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_loadrpt_batch COMMIT: bizdate=%s 채널수=%ld 총부하=%ld 최대부하=%ld\n",
bizdate, rec.channels, rec.load_sum, rec.busiest_load);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,53 @@
/*
* mg_mtiroll_batch.pgc - mg 전문유형(MTI)별 금액/건수 롤업 배치 (커서 + XA).
*
* 당일 전문을 MTI 로 그룹핑해 유형별 건수/금액합을 커서로 훑으며 총계를 누적하고,
* 금액합이 가장 큰 유형을 찾아낸다 (전문 통계 유형별 세분의 일배치판).
* Usage: mg_mtiroll_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_mtiroll_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_mti[8];
long h_cnt, h_amt;
EXEC SQL END DECLARE SECTION;
mg_mtiroll_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE mrc CURSOR FOR
SELECT mti, count(*), coalesce(sum(amount),0)
FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY mti ORDER BY mti;
EXEC SQL OPEN mrc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH mrc INTO :h_mti, :h_cnt, :h_amt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mrc; tpabort(0); return 1; }
rec.groups++;
rec.total_cnt += h_cnt;
rec.total_amt += h_amt;
if (h_amt > rec.top_amt) rec.top_amt = h_amt;
}
EXEC SQL CLOSE mrc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_mtiroll_batch COMMIT: bizdate=%s 유형수=%ld 총건=%ld 총금액=%ld 최대유형금액=%ld\n",
bizdate, rec.groups, rec.total_cnt, rec.total_amt, rec.top_amt);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,52 @@
/*
* mg_peakhour_batch.pgc - mg 시간대별 피크 트래픽 탐지 배치 (커서 + XA).
*
* 당일 전문을 생성시각의 시(0~23)로 그룹핑해 시간대별 건수를 커서로 훑으며
* 최다 건수 시간대를 찾는다 (시간대별 통계에서 피크만 뽑는 일배치판).
* Usage: mg_peakhour_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_peakhour_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_hour, h_cnt;
EXEC SQL END DECLARE SECTION;
mg_peakhour_rec_t rec;
memset(&rec, 0, sizeof(rec));
rec.peak_hour = -1;
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE phc CURSOR FOR
SELECT extract(hour from created_at)::int AS hh, count(*)
FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY hh ORDER BY hh;
EXEC SQL OPEN phc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH phc INTO :h_hour, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE phc; tpabort(0); return 1; }
rec.buckets++;
if (h_cnt > rec.peak_cnt) { rec.peak_cnt = h_cnt; rec.peak_hour = h_hour; }
}
EXEC SQL CLOSE phc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_peakhour_batch COMMIT: bizdate=%s 활동시간대=%ld 피크시=%ld시 피크건수=%ld\n",
bizdate, rec.buckets, rec.peak_hour, rec.peak_cnt);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,52 @@
/*
* mg_rcfreq_batch.pgc - mg 응답코드별 빈도/오류율 집계 배치 (커서 + XA).
*
* 당일 전문을 응답코드(rc)로 그룹핑해 코드 종류 수와 전체 건수를 커서로 누적하고,
* 정상('00') 외 건수로 오류율(bps)을 산출한다 (응답코드별 통계의 일배치판).
* Usage: mg_rcfreq_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_rcfreq_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_rc[4];
long h_cnt;
EXEC SQL END DECLARE SECTION;
mg_rcfreq_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE rfc CURSOR FOR
SELECT rc, count(*) FROM mg_msg_log WHERE biz_date = :h_bizdate
GROUP BY rc ORDER BY rc;
EXEC SQL OPEN rfc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH rfc INTO :h_rc, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rfc; tpabort(0); return 1; }
rec.rc_kinds++;
rec.total += h_cnt;
if (strcmp(h_rc, "00") != 0) rec.err += h_cnt;
}
EXEC SQL CLOSE rfc;
rec.err_bps = (rec.total > 0) ? (rec.err * 10000L) / rec.total : 0;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_rcfreq_batch COMMIT: bizdate=%s 코드종류=%ld 총건=%ld 오류건=%ld 오류율=%ldbps\n",
bizdate, rec.rc_kinds, rec.total, rec.err, rec.err_bps);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,51 @@
/*
* mg_routechk_batch.pgc - mg 라우팅 룰 감사 배치 (커서 + XA).
*
* 전 라우팅 룰을 채널+MTI 순으로 커서 훑으며 비활성 룰 수를 세고, 직전 키와 같은
* (채널,MTI) 활성 룰이 연달아 나오면 중복 라우팅으로 집계한다 (라우팅 룰 관리 감사).
* Usage: mg_routechk_batch
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_routechk_batch.h"
int main(int argc, char **argv)
{
(void)argc; (void)argv;
char prevkey[32] = "", curkey[32];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_mti[8];
int h_active;
EXEC SQL END DECLARE SECTION;
mg_routechk_rec_t rec;
memset(&rec, 0, sizeof(rec));
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE rkc CURSOR FOR
SELECT channel, mti, active FROM mg_route ORDER BY channel, mti, route_id;
EXEC SQL OPEN rkc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH rkc INTO :h_chan, :h_mti, :h_active;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rkc; tpabort(0); return 1; }
rec.rules++;
if (!h_active) { rec.inactive++; continue; }
snprintf(curkey, sizeof(curkey), "%s|%s", h_chan, h_mti);
if (strcmp(curkey, prevkey) == 0) rec.dup_keys++; /* 같은 키 활성 룰 중복 */
strcpy(prevkey, curkey);
}
EXEC SQL CLOSE rkc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_routechk_batch COMMIT: 룰수=%ld 비활성=%ld 중복키=%ld\n",
rec.rules, rec.inactive, rec.dup_keys);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,69 @@
/*
* mg_seqrepair_batch.pgc - mg 채널별 순번 결번 보정 통계 배치 (중첩 커서 + XA).
*
* 채널을 바깥 커서로, 각 채널의 STAN 을 안쪽 커서로 오름차순 훑으며 채널마다
* 결번(간격>1) 유무와 최대 결번폭을 잰다 (순번 갭 검출을 채널단위로 세분한 일배치).
* Usage: mg_seqrepair_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include "mg_seqrepair_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
long prev, chan_gaps;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_chan[16];
long h_stan;
EXEC SQL END DECLARE SECTION;
mg_seqrepair_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
EXEC SQL DECLARE srchan CURSOR FOR
SELECT DISTINCT channel FROM mg_msg_log WHERE biz_date = :h_bizdate ORDER BY channel;
EXEC SQL DECLARE srstan CURSOR FOR
SELECT DISTINCT stan FROM mg_msg_log
WHERE biz_date = :h_bizdate AND channel = :h_chan ORDER BY stan;
EXEC SQL OPEN srchan;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH srchan INTO :h_chan;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE srchan; tpabort(0); return 1; }
rec.channels++;
prev = -1; chan_gaps = 0;
EXEC SQL OPEN srstan;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE srchan; tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH srstan INTO :h_stan;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE srstan; EXEC SQL CLOSE srchan; tpabort(0); return 1; }
if (prev >= 0 && h_stan - prev > 1) {
chan_gaps++;
if (h_stan - prev - 1 > rec.widest) rec.widest = h_stan - prev - 1;
}
prev = h_stan;
}
EXEC SQL CLOSE srstan;
if (chan_gaps > 0) rec.gap_chans++;
}
EXEC SQL CLOSE srchan;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_seqrepair_batch COMMIT: bizdate=%s 채널수=%ld 결번채널=%ld 최대결번폭=%ld\n",
bizdate, rec.channels, rec.gap_chans, rec.widest);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,61 @@
/*
* mg_throttle_batch.pgc - mg 채널 유입 임계 초과 탐지 배치 (커서 + XA).
*
* mg_seq(THROTLIM) 채널 유입한도를 읽어, 채널별 당일 수신(IN) 건수를 커서로 훑으며
* 한도 초과 채널 수와 최다 유입 채널의 건수를 집계한다 (트래픽 제어의 일배치판).
* Usage: mg_throttle_batch [YYYY-MM-DD]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atmi.h>
#include <userlog.h>
#include "mg_throttle_batch.h"
int main(int argc, char **argv)
{
const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19";
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16], h_chan[16];
long h_cnt, h_lim;
EXEC SQL END DECLARE SECTION;
mg_throttle_rec_t rec;
memset(&rec, 0, sizeof(rec));
strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }
if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; }
h_lim = 10000;
EXEC SQL SELECT seq_val INTO :h_lim FROM mg_seq WHERE seq_name = 'THROTLIM';
if (sqlca.sqlcode < 0) { fprintf(stderr, "THROTLIM 조회 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; }
if (h_lim <= 0) h_lim = 10000;
EXEC SQL DECLARE thc CURSOR FOR
SELECT channel, count(*) FROM mg_msg_log
WHERE biz_date = :h_bizdate AND direction = 'IN'
GROUP BY channel ORDER BY channel;
EXEC SQL OPEN thc;
if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; }
for (;;) {
EXEC SQL FETCH thc INTO :h_chan, :h_cnt;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE thc; tpabort(0); return 1; }
rec.channels++;
if (h_cnt > rec.busiest) rec.busiest = h_cnt;
if (h_cnt >= h_lim) {
rec.over_cnt++;
userlog("mg_throttle_batch 유입초과 채널=%s %ld/%ld", h_chan, h_cnt, h_lim);
}
}
EXEC SQL CLOSE thc;
if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; }
printf(">>> mg_throttle_batch COMMIT: bizdate=%s 채널수=%ld 초과채널=%ld 최다유입=%ld (한도=%ld)\n",
bizdate, rec.channels, rec.over_cnt, rec.busiest, h_lim);
tpclose(); tpterm();
return 0;
}

View file

@ -0,0 +1,10 @@
/*
* mg_chanfail_batch.h - mg mg_chanfail_batch (copybook).
* . Included by mg_chanfail_batch.pgc.
*/
#ifndef MG_CHANFAIL_BATCH_H
#define MG_CHANFAIL_BATCH_H
typedef struct { long channels; long down_cnt; long up_cnt; } mg_chanfail_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_dupmark_batch.h - mg mg_dupmark_batch (copybook). STAN
* . Included by mg_dupmark_batch.pgc.
*/
#ifndef MG_DUPMARK_BATCH_H
#define MG_DUPMARK_BATCH_H
typedef struct { long dup_stans; long marked; } mg_dupmark_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_errescal_batch.h - mg mg_errescal_batch (copybook).
* . Included by mg_errescal_batch.pgc.
*/
#ifndef MG_ERRESCAL_BATCH_H
#define MG_ERRESCAL_BATCH_H
typedef struct { long scanned; long escalated; } mg_errescal_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_healthz_batch.h - mg mg_healthz_batch (copybook).
* . Included by mg_healthz_batch.pgc.
*/
#ifndef MG_HEALTHZ_BATCH_H
#define MG_HEALTHZ_BATCH_H
typedef struct { long chan_up; long queue_backlog; long err_today; int healthy; } mg_healthz_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_loadrpt_batch.h - mg mg_loadrpt_batch (copybook).
* . Included by mg_loadrpt_batch.pgc.
*/
#ifndef MG_LOADRPT_BATCH_H
#define MG_LOADRPT_BATCH_H
typedef struct { long channels; long load_sum; long busiest_load; } mg_loadrpt_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_mtiroll_batch.h - mg mg_mtiroll_batch (copybook). MTI별 /
* . Included by mg_mtiroll_batch.pgc.
*/
#ifndef MG_MTIROLL_BATCH_H
#define MG_MTIROLL_BATCH_H
typedef struct { long groups; long total_cnt; long total_amt; long top_amt; } mg_mtiroll_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_peakhour_batch.h - mg mg_peakhour_batch (copybook).
* . Included by mg_peakhour_batch.pgc.
*/
#ifndef MG_PEAKHOUR_BATCH_H
#define MG_PEAKHOUR_BATCH_H
typedef struct { long buckets; long peak_hour; long peak_cnt; } mg_peakhour_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_rcfreq_batch.h - mg mg_rcfreq_batch (copybook). /
* . Included by mg_rcfreq_batch.pgc.
*/
#ifndef MG_RCFREQ_BATCH_H
#define MG_RCFREQ_BATCH_H
typedef struct { long rc_kinds; long total; long err; long err_bps; } mg_rcfreq_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_routechk_batch.h - mg mg_routechk_batch (copybook). (/)
* . Included by mg_routechk_batch.pgc.
*/
#ifndef MG_ROUTECHK_BATCH_H
#define MG_ROUTECHK_BATCH_H
typedef struct { long rules; long inactive; long dup_keys; } mg_routechk_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_seqrepair_batch.h - mg mg_seqrepair_batch (copybook).
* . Included by mg_seqrepair_batch.pgc.
*/
#ifndef MG_SEQREPAIR_BATCH_H
#define MG_SEQREPAIR_BATCH_H
typedef struct { long channels; long gap_chans; long widest; } mg_seqrepair_rec_t;
#endif

View file

@ -0,0 +1,10 @@
/*
* mg_throttle_batch.h - mg mg_throttle_batch (copybook).
* . Included by mg_throttle_batch.pgc.
*/
#ifndef MG_THROTTLE_BATCH_H
#define MG_THROTTLE_BATCH_H
typedef struct { long channels; long over_cnt; long busiest; } mg_throttle_rec_t;
#endif

View file

@ -2,13 +2,13 @@
* mg_svr.pgc - mg (전문게이트웨이 / message gateway) MODULE SERVER (thin dispatcher).
*
* The legacy "one module server, many services" pattern: this binary owns NO
* business logic. Each of mg's 75 online services now lives in its own file
* business logic. Each of mg's 133 online services now lives in its own file
* (app/src/mg/svc/<SVCNAME>.pgc) with its own copybook header
* (app/src/mg/svc/<SVCNAME>.h); those objects are archived into libmgsvc.a
* and linked into this server. Here we only:
* - include every service copybook (for the extern prototypes),
* - tpopen() the ECPG XA RM,
* - tpadvertise() all 75 services from a {name, fn} table,
* - tpadvertise() all 133 services from a {name, fn} table,
* - tpclose() at shutdown.
*
* XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so);
@ -103,6 +103,76 @@
#include "MG_SETTLEREQ.h"
#include "MG_SETTLERSP.h"
#include "MG_QDEPTH.h"
/* ===== 스케일아웃 신규 서비스 (+58) ===== */
/* --- 전문유형별 처리 세분 (0100/0110/0120/0200/0210/0220/0400/0420/0800/0810) --- */
#include "MG_AUTHADV.h"
#include "MG_AUTHADVRSP.h"
#include "MG_FINREQ.h"
#include "MG_FINRSP.h"
#include "MG_FINADV.h"
#include "MG_FINADVRSP.h"
#include "MG_REVRSP.h"
#include "MG_REVADVRSP.h"
#include "MG_NMRSP.h"
#include "MG_NMADVICE.h"
#include "MG_BALINQ.h"
#include "MG_INQRSP.h"
#include "MG_CARDVER.h"
#include "MG_PREAUTH.h"
/* --- 채널별 어댑터 (VAN사별/직연동/중계) --- */
#include "MG_VAN3ADPT.h"
#include "MG_VAN4ADPT.h"
#include "MG_RELAYADPT.h"
#include "MG_ISOADPT.h"
#include "MG_HOSTADPT.h"
#include "MG_CHANMAP.h"
#include "MG_FEP.h"
/* --- 전문 조립/분해/변환 (내부<->ISO8583) --- */
#include "MG_ENCODE.h"
#include "MG_DECODE.h"
#include "MG_FIELDGET.h"
#include "MG_FIELDSET.h"
#include "MG_BMPPARSE.h"
#include "MG_BMPMERGE.h"
/* --- MAC/키 관리 세분 --- */
#include "MG_MACSESSION.h"
#include "MG_KEYLOAD.h"
#include "MG_KEYCHECK.h"
#include "MG_PINBLOCK.h"
#include "MG_KEYEXPIRE.h"
/* --- 타임아웃/재전송/중복검출 세분 --- */
#include "MG_RESNDONE.h"
#include "MG_STALEMARK.h"
#include "MG_DUPPURGE.h"
#include "MG_INFLIGHT.h"
/* --- 순번 갭 검출/보정 --- */
#include "MG_SEQNEXT.h"
#include "MG_SEQRESET.h"
#include "MG_SEQFILL.h"
/* --- 라우팅 룰 관리 --- */
#include "MG_RTFIND.h"
#include "MG_RTDISABLE.h"
#include "MG_RTENABLE.h"
#include "MG_RTCOUNT.h"
/* --- 채널 상태 모니터/이중화 전환 --- */
#include "MG_CHANPING.h"
#include "MG_CHANFAILOVER.h"
#include "MG_CHANLIST.h"
#include "MG_CHANRESET.h"
/* --- 전문 통계 (채널/금액/응답코드/시간대/종합) --- */
#include "MG_STATCHAN.h"
#include "MG_STATAMT.h"
#include "MG_STATERR.h"
#include "MG_STATPEAK.h"
#include "MG_STATSUM.h"
/* --- 오류전문 재처리 워크플로 --- */
#include "MG_ERRREQUEUE.h"
#include "MG_ERRCLASS.h"
#include "MG_ERRESCAL.h"
/* --- 트래픽 제어/부하분산 + 헬스체크 세분 --- */
#include "MG_LOADBAL.h"
#include "MG_RATELIMIT.h"
#include "MG_READYZ.h"
/* ----- advertise table: {service name, function} (fn is extern from its file) */
static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
@ -144,6 +214,36 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = {
{"MG_AMTCHECK", MG_AMTCHECK}, {"MG_ADVICE", MG_ADVICE},
{"MG_SETTLEREQ", MG_SETTLEREQ}, {"MG_SETTLERSP", MG_SETTLERSP},
{"MG_QDEPTH", MG_QDEPTH},
/* ===== 스케일아웃 신규 서비스 (+58) ===== */
{"MG_AUTHADV", MG_AUTHADV}, {"MG_AUTHADVRSP", MG_AUTHADVRSP},
{"MG_FINREQ", MG_FINREQ}, {"MG_FINRSP", MG_FINRSP},
{"MG_FINADV", MG_FINADV}, {"MG_FINADVRSP", MG_FINADVRSP},
{"MG_REVRSP", MG_REVRSP}, {"MG_REVADVRSP", MG_REVADVRSP},
{"MG_NMRSP", MG_NMRSP}, {"MG_NMADVICE", MG_NMADVICE},
{"MG_BALINQ", MG_BALINQ}, {"MG_INQRSP", MG_INQRSP},
{"MG_CARDVER", MG_CARDVER}, {"MG_PREAUTH", MG_PREAUTH},
{"MG_VAN3ADPT", MG_VAN3ADPT}, {"MG_VAN4ADPT", MG_VAN4ADPT},
{"MG_RELAYADPT", MG_RELAYADPT}, {"MG_ISOADPT", MG_ISOADPT},
{"MG_HOSTADPT", MG_HOSTADPT}, {"MG_CHANMAP", MG_CHANMAP},
{"MG_FEP", MG_FEP}, {"MG_ENCODE", MG_ENCODE},
{"MG_DECODE", MG_DECODE}, {"MG_FIELDGET", MG_FIELDGET},
{"MG_FIELDSET", MG_FIELDSET}, {"MG_BMPPARSE", MG_BMPPARSE},
{"MG_BMPMERGE", MG_BMPMERGE}, {"MG_MACSESSION", MG_MACSESSION},
{"MG_KEYLOAD", MG_KEYLOAD}, {"MG_KEYCHECK", MG_KEYCHECK},
{"MG_PINBLOCK", MG_PINBLOCK}, {"MG_KEYEXPIRE", MG_KEYEXPIRE},
{"MG_RESNDONE", MG_RESNDONE}, {"MG_STALEMARK", MG_STALEMARK},
{"MG_DUPPURGE", MG_DUPPURGE}, {"MG_INFLIGHT", MG_INFLIGHT},
{"MG_SEQNEXT", MG_SEQNEXT}, {"MG_SEQRESET", MG_SEQRESET},
{"MG_SEQFILL", MG_SEQFILL}, {"MG_RTFIND", MG_RTFIND},
{"MG_RTDISABLE", MG_RTDISABLE}, {"MG_RTENABLE", MG_RTENABLE},
{"MG_RTCOUNT", MG_RTCOUNT}, {"MG_CHANPING", MG_CHANPING},
{"MG_CHANFAILOVER", MG_CHANFAILOVER}, {"MG_CHANLIST", MG_CHANLIST},
{"MG_CHANRESET", MG_CHANRESET}, {"MG_STATCHAN", MG_STATCHAN},
{"MG_STATAMT", MG_STATAMT}, {"MG_STATERR", MG_STATERR},
{"MG_STATPEAK", MG_STATPEAK}, {"MG_STATSUM", MG_STATSUM},
{"MG_ERRREQUEUE", MG_ERRREQUEUE}, {"MG_ERRCLASS", MG_ERRCLASS},
{"MG_ERRESCAL", MG_ERRESCAL}, {"MG_LOADBAL", MG_LOADBAL},
{"MG_RATELIMIT", MG_RATELIMIT}, {"MG_READYZ", MG_READYZ},
{NULL, NULL}
};

View file

@ -0,0 +1,12 @@
/*
* MG_AUTHADV.h - mg () MG_AUTHADV (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_AUTHADV_H
#define MG_AUTHADV_H
#include "mg_svc.h"
void MG_AUTHADV(TPSVCINFO *p);
#endif /* MG_AUTHADV_H */

View file

@ -0,0 +1,33 @@
/*
* MG_AUTHADV.pgc - mg 모듈 서비스 MG_AUTHADV (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_AUTHADV.h"
/* MG_AUTHADV - 0120 승인통지(advice) 수신: 승인응답 유실 후 단말이 재차 보낸
* 통지 전문을 원장에 0120/IN 으로 채번·적재하고 통지수신(A) 상태로 표시한다. */
void MG_AUTHADV(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], proc[8], bizdate[16];
long stan, msg_id, amount, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(proc, 0, sizeof(proc));
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
amount = (n >= 22) ? atol(raw + 10) : getl(b, T_AMOUNT);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0120", chan, "IN", proc, "", amount,
"", "00", raw, "A", bizdate) < 0) FAIL(b);
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_AUTHADVRSP.h - mg () MG_AUTHADVRSP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_AUTHADVRSP_H
#define MG_AUTHADVRSP_H
#include "mg_svc.h"
void MG_AUTHADVRSP(TPSVCINFO *p);
#endif /* MG_AUTHADVRSP_H */

View file

@ -0,0 +1,33 @@
/*
* MG_AUTHADVRSP.pgc - mg 모듈 서비스 MG_AUTHADVRSP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_AUTHADVRSP.h"
/* MG_AUTHADVRSP - 0130 승인통지응답 생성: 원 STAN 의 0120 통지를 원장에서 찾아
* 0130 응답전문을 OUT 으로 채번·적재하고, 원 통지를 확인완료(C) 상태로 갱신. */
void MG_AUTHADVRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16];
long orig_stan, orig_id, stan, msg_id;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
orig_stan = getl(b, T_ARG1);
if (orig_stan <= 0) { setl(b, T_RC, 13); FAIL(b); }
orig_id = mgdb_find_msg_by_stan(orig_stan);
if (orig_id < 0) { setl(b, T_RC, 25); FAIL(b); } /* 원 통지 없음 */
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0130", chan, "OUT", "000000", "", 0,
"", "00", "", "S", bizdate) < 0) FAIL(b);
if (mgdb_update_msg_rc(orig_id, "00", "C") < 0) FAIL(b);
setl(b, T_ID1, msg_id);
setl(b, T_ID2, orig_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_BALINQ.h - mg () MG_BALINQ (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_BALINQ_H
#define MG_BALINQ_H
#include "mg_svc.h"
void MG_BALINQ(TPSVCINFO *p);
#endif /* MG_BALINQ_H */

View file

@ -0,0 +1,38 @@
/*
* MG_BALINQ.pgc - mg 모듈 서비스 MG_BALINQ (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_BALINQ.h"
/* MG_BALINQ - 0100 잔액조회 요청 수신: 금액 0 · 처리코드 31xxxx(조회) 인 전문을
* PAN Luhn 검증 후 원장에 0100/IN 으로 적재. 처리코드가 조회계열(31)이 아니면 rc=12. */
void MG_BALINQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], bizdate[16], proc[8], pan[24], rc[4];
long stan, msg_id, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(proc, 0, sizeof(proc)); memset(pan, 0, sizeof(pan));
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "310000");
if (n >= 38) strncpy(pan, raw + 22, 16);
if (strncmp(proc, "31", 2) != 0) strcpy(rc, "12"); /* 12 = 처리불가 거래 */
else if (pan[0] && !acq_luhn_ok(pan)) strcpy(rc, "14");
else strcpy(rc, "00");
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0100", chan, "IN", proc, "", 0,
"", rc, raw, "R", bizdate) < 0) FAIL(b);
sets_(b, T_STR2, rc);
sets_(b, T_STR4, proc);
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_BMPMERGE.h - mg () MG_BMPMERGE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_BMPMERGE_H
#define MG_BMPMERGE_H
#include "mg_svc.h"
void MG_BMPMERGE(TPSVCINFO *p);
#endif /* MG_BMPMERGE_H */

View file

@ -0,0 +1,29 @@
/*
* MG_BMPMERGE.pgc - mg 모듈 서비스 MG_BMPMERGE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_BMPMERGE.h"
/* MG_BMPMERGE - 비트맵 병합(순수계산): 1차 비트맵(T_STR6)과 2차 비트맵(T_STR7)을
* OR 로 합쳐 통합 마스크를 만들고, 2차 존재 시 필드1(확장비트) 을 세워 회신. */
void MG_BMPMERGE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char hex1[24], hex2[24], out[24];
unsigned long m1 = 0, m2 = 0, merged;
gets_(b, T_STR6, hex1, sizeof(hex1));
gets_(b, T_STR7, hex2, sizeof(hex2));
if (hex1[0] && sscanf(hex1, "%16lx", &m1) != 1) { setl(b, T_RC, 30); FAIL(b); }
if (hex2[0] && sscanf(hex2, "%16lx", &m2) != 1) { setl(b, T_RC, 30); FAIL(b); }
merged = m1 | m2;
if (m2 != 0) merged |= (1UL << (64 - 1)); /* 2차비트맵 존재표시 = 필드1 */
sprintf(out, "%016lX", merged);
sets_(b, T_STR6, out);
setl(b, T_ARG3, (long)(m2 != 0));
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_BMPPARSE.h - mg () MG_BMPPARSE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_BMPPARSE_H
#define MG_BMPPARSE_H
#include "mg_svc.h"
void MG_BMPPARSE(TPSVCINFO *p);
#endif /* MG_BMPPARSE_H */

View file

@ -0,0 +1,34 @@
/*
* MG_BMPPARSE.pgc - mg 모듈 서비스 MG_BMPPARSE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_BMPPARSE.h"
/* MG_BMPPARSE - 비트맵 해석(순수계산): 16-hex 1차 비트맵을 64비트로 풀어 존재하는
* 필드번호 목록(콤마구분)과 존재 필드 개수를 회신. hex 형식 오류면 rc=30. */
void MG_BMPPARSE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char hex[24], list[256], tmp[8];
unsigned long mask = 0;
long cnt = 0;
int i;
gets_(b, T_STR6, hex, sizeof(hex));
if (strlen(hex) < 16) { setl(b, T_RC, 30); FAIL(b); }
if (sscanf(hex, "%16lx", &mask) != 1) { setl(b, T_RC, 30); FAIL(b); }
list[0] = 0;
for (i = 1; i <= 64; i++) { /* 비트 i = 필드 i (MSB=필드1) */
if (mask & (1UL << (64 - i))) {
sprintf(tmp, "%s%d", (cnt ? "," : ""), i);
if (strlen(list) + strlen(tmp) < sizeof(list) - 1) strcat(list, tmp);
cnt++;
}
}
sets_(b, T_STR3, list);
setl(b, T_COUNT, cnt);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CARDVER.h - mg () MG_CARDVER (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_CARDVER_H
#define MG_CARDVER_H
#include "mg_svc.h"
void MG_CARDVER(TPSVCINFO *p);
#endif /* MG_CARDVER_H */

View file

@ -0,0 +1,45 @@
/*
* MG_CARDVER.pgc - mg 모듈 서비스 MG_CARDVER (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_CARDVER.h"
/* ----- service-local pure helper(s) ----- */
/* PAN 앞6(BIN)만 남기고 뒤 전부 마스킹 (카드검증 로그용, 뒤4 미노출). */
static void mg_cardver_mask(const char *pan, char *out /* >= 20 */)
{
int i, len = (int)strlen(pan);
for (i = 0; i < len && i < 19; i++)
out[i] = (i < 6) ? pan[i] : '*';
out[i] = 0;
}
/* MG_CARDVER - 0100 카드검증(금액 0 승인): PAN 자릿수(13~19)와 Luhn 을 함께 검사해
* 유효(00)/무효(14)/포맷오류(30)를 판정하고 BIN6 마스킹 원장을 적재한다. */
void MG_CARDVER(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], pan[24], masked[24], rc[4], bizdate[16];
long stan, msg_id, plen;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR5, pan, sizeof(pan));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
plen = (long)strlen(pan);
if (plen < 13 || plen > 19) strcpy(rc, "30");
else if (!acq_luhn_ok(pan)) strcpy(rc, "14");
else strcpy(rc, "00");
mg_cardver_mask(pan, masked);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0100", chan, "IN", "000000", "", 0,
masked, rc, "", "R", bizdate) < 0) FAIL(b);
sets_(b, T_STR2, rc);
sets_(b, T_STR6, masked);
setl(b, T_COUNT, plen);
setl(b, T_ID1, msg_id);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANFAILOVER.h - mg () MG_CHANFAILOVER (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_CHANFAILOVER_H
#define MG_CHANFAILOVER_H
#include "mg_svc.h"
void MG_CHANFAILOVER(TPSVCINFO *p);
#endif /* MG_CHANFAILOVER_H */

View file

@ -0,0 +1,42 @@
/*
* MG_CHANFAILOVER.pgc - mg 모듈 서비스 MG_CHANFAILOVER (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_CHANFAILOVER.h"
/* MG_CHANFAILOVER - 이중화 절체: 주채널(T_CHANNEL)이 DOWN 이면 예비채널(T_STR1)을
* UP 으로 승격하고 주채널을 DOWN 확정. 주채널이 살아있으면 절체 없이 rc=1. */
void MG_CHANFAILOVER(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char primary[16], backup[16], curp[16], curb[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_primary[16], h_backup[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, primary, sizeof(primary)); if (primary[0] == 0) strcpy(primary, "CH01");
gets_(b, T_STR1, backup, sizeof(backup)); if (backup[0] == 0) strcpy(backup, "CH02");
if (mgdb_get_channel_status(primary, curp) < 0) { setl(b, T_RC, 25); FAIL(b); }
if (mgdb_get_channel_status(backup, curb) < 0) { setl(b, T_RC, 25); FAIL(b); }
if (strcmp(curp, "UP") == 0) { /* 주채널 정상 = 절체 불필요 */
sets_(b, T_STATUS, "PRIMARY");
setl(b, T_RC, 1);
OK(b);
}
strncpy(h_primary, primary, sizeof(h_primary)-1); h_primary[sizeof(h_primary)-1] = 0;
strncpy(h_backup, backup, sizeof(h_backup)-1); h_backup[sizeof(h_backup)-1] = 0;
EXEC SQL UPDATE mg_channel SET status = 'DOWN', updated_at = now() WHERE channel = :h_primary;
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE mg_channel SET status = 'UP', signon_at = now(), updated_at = now()
WHERE channel = :h_backup;
if (sqlca.sqlcode < 0) FAIL(b);
userlog("MG_CHANFAILOVER 절체 %s(DOWN) -> %s(UP)", primary, backup);
sets_(b, T_STATUS, "FAILOVER");
sets_(b, T_STR5, backup);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANLIST.h - mg () MG_CHANLIST (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_CHANLIST_H
#define MG_CHANLIST_H
#include "mg_svc.h"
void MG_CHANLIST(TPSVCINFO *p);
#endif /* MG_CHANLIST_H */

View file

@ -0,0 +1,42 @@
/*
* MG_CHANLIST.pgc - mg 모듈 서비스 MG_CHANLIST (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_CHANLIST.h"
/* MG_CHANLIST - 채널 상태 요약: 전체 채널 중 UP/DOWN 개수를 집계하고, UP 채널ID 를
* 콤마로 이어 목록으로 회신. UP 이 하나도 없으면 rc=1(전면 다운) 경고. */
void MG_CHANLIST(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char list[256], tmp[24];
long up = 0, down = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_status[16];
EXEC SQL END DECLARE SECTION;
list[0] = 0;
EXEC SQL DECLARE clst CURSOR FOR
SELECT channel, status FROM mg_channel ORDER BY channel;
EXEC SQL OPEN clst;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH clst INTO :h_chan, :h_status;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clst; FAIL(b); }
if (strcmp(h_status, "UP") == 0) {
up++;
sprintf(tmp, "%s%s", (up > 1 ? "," : ""), h_chan);
if (strlen(list) + strlen(tmp) < sizeof(list) - 1) strcat(list, tmp);
} else {
down++;
}
}
EXEC SQL CLOSE clst;
sets_(b, T_STR3, list);
setl(b, T_ARG2, up);
setl(b, T_ARG3, down);
setl(b, T_RC, (up == 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANMAP.h - mg () MG_CHANMAP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_CHANMAP_H
#define MG_CHANMAP_H
#include "mg_svc.h"
void MG_CHANMAP(TPSVCINFO *p);
#endif /* MG_CHANMAP_H */

View file

@ -0,0 +1,26 @@
/*
* MG_CHANMAP.pgc - mg 모듈 서비스 MG_CHANMAP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_CHANMAP.h"
/* MG_CHANMAP - 외부 채널코드 -> 내부 채널ID 매핑: VAN1/VAN2->CH01, EDC->CH02,
* FEP/직연동->CH03 규칙으로 정규화하고, 미등록 외부코드는 rc=25 로 반려. */
void MG_CHANMAP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char ext[24], chan[16];
gets_(b, T_STR1, ext, sizeof(ext));
if (ext[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
if (strcmp(ext, "VAN1") == 0 || strcmp(ext, "VAN2") == 0) strcpy(chan, "CH01");
else if (strcmp(ext, "EDC") == 0 || strcmp(ext, "POS") == 0) strcpy(chan, "CH02");
else if (strcmp(ext, "FEP") == 0 || strcmp(ext, "ISO") == 0) strcpy(chan, "CH03");
else { userlog("MG_CHANMAP 미등록 채널코드 %s", ext); setl(b, T_RC, 25); FAIL(b); }
sets_(b, T_CHANNEL, chan);
sets_(b, T_STR5, ext);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANPING.h - mg () MG_CHANPING (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_CHANPING_H
#define MG_CHANPING_H
#include "mg_svc.h"
void MG_CHANPING(TPSVCINFO *p);
#endif /* MG_CHANPING_H */

View file

@ -0,0 +1,33 @@
/*
* MG_CHANPING.pgc - mg 모듈 서비스 MG_CHANPING (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_CHANPING.h"
/* MG_CHANPING - 채널 하트비트: UP 채널의 updated_at 을 now() 로 눌러 살아있음을
* 기록한다. DOWN 채널이면 rc=1 로 핑 실패를 알리고 갱신하지 않는다. */
void MG_CHANPING(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], cur[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
if (mgdb_get_channel_status(chan, cur) < 0) { setl(b, T_RC, 25); FAIL(b); }
if (strcmp(cur, "UP") != 0) {
sets_(b, T_STATUS, cur);
setl(b, T_RC, 1); /* 살아있지 않은 채널 */
OK(b);
}
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
EXEC SQL UPDATE mg_channel SET updated_at = now() WHERE channel = :h_chan;
if (sqlca.sqlcode < 0) FAIL(b);
sets_(b, T_STATUS, "UP");
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_CHANRESET.h - mg () MG_CHANRESET (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_CHANRESET_H
#define MG_CHANRESET_H
#include "mg_svc.h"
void MG_CHANRESET(TPSVCINFO *p);
#endif /* MG_CHANRESET_H */

View file

@ -0,0 +1,32 @@
/*
* MG_CHANRESET.pgc - mg 모듈 서비스 MG_CHANRESET (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_CHANRESET.h"
/* MG_CHANRESET - 채널 세션 초기화: 채널을 DOWN 으로 내리고 세션키/사인온시각/last_stan
* 을 일괄 비워 재개통(사인온) 이전 상태로 되돌린다. 초기화 전 상태를 회신. */
void MG_CHANRESET(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], cur[16];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
if (mgdb_get_channel_status(chan, cur) < 0) { setl(b, T_RC, 25); FAIL(b); }
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
EXEC SQL UPDATE mg_channel
SET status = 'DOWN', session_key = '', signon_at = NULL,
last_stan = 0, updated_at = now()
WHERE channel = :h_chan;
if (sqlca.sqlcode < 0) { userlog("MG_CHANRESET FAIL [%d]", sqlca.sqlcode); FAIL(b); }
userlog("MG_CHANRESET 채널 초기화 %s (%s -> DOWN)", chan, cur);
sets_(b, T_STR5, cur); /* 초기화 전 상태 */
sets_(b, T_STATUS, "DOWN");
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_DECODE.h - mg () MG_DECODE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_DECODE_H
#define MG_DECODE_H
#include "mg_svc.h"
void MG_DECODE(TPSVCINFO *p);
#endif /* MG_DECODE_H */

View file

@ -0,0 +1,33 @@
/*
* MG_DECODE.pgc - mg 모듈 서비스 MG_DECODE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_DECODE.h"
/* MG_DECODE - 확장 고정길이 해체(순수계산): MG_ENCODE 가 만든 32자 프레임을
* MTI/처리코드/금액/STAN/채널 필드로 되돌린다. 길이 32 미만이면 rc=31. */
void MG_DECODE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[64], mti[8], proc[8], chan[8];
long amount, stan, n;
gets_(b, T_STR3, raw, sizeof(raw));
n = (long)strlen(raw);
if (n < 32) { setl(b, T_RC, 31); FAIL(b); }
memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc)); memset(chan, 0, sizeof(chan));
strncpy(mti, raw, 4);
strncpy(proc, raw + 4, 6);
amount = atol(raw + 10); /* [10..21] 금액 12 */
stan = atol(raw + 22); /* [22..27] STAN 6 */
strncpy(chan, raw + 28, 4); /* [28..31] 채널 4 */
sets_(b, T_STR1, mti);
sets_(b, T_STR4, proc);
setl(b, T_AMOUNT, amount);
setl(b, T_ARG1, stan);
sets_(b, T_CHANNEL, chan);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_DUPPURGE.h - mg () MG_DUPPURGE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_DUPPURGE_H
#define MG_DUPPURGE_H
#include "mg_svc.h"
void MG_DUPPURGE(TPSVCINFO *p);
#endif /* MG_DUPPURGE_H */

View file

@ -0,0 +1,37 @@
/*
* MG_DUPPURGE.pgc - mg 모듈 서비스 MG_DUPPURGE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_DUPPURGE.h"
/* MG_DUPPURGE - 중복전문 정리: 동일 STAN 이 2건 이상이면 최소 msg_id(원본)만 남기고
* 나머지 중복본을 rc=94/status=X(폐기)로 마킹. 폐기 건수를 회신한다. */
void MG_DUPPURGE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long purged;
EXEC SQL BEGIN DECLARE SECTION;
long h_stan, h_keep;
char h_bizdate[16];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
h_stan = getl(b, T_ARG1);
if (h_stan <= 0) { setl(b, T_RC, 13); FAIL(b); }
EXEC SQL SELECT min(msg_id) INTO :h_keep
FROM mg_msg_log WHERE stan = :h_stan AND biz_date = :h_bizdate;
if (sqlca.sqlcode == 100) { setl(b, T_COUNT, 0); OK(b); }
if (sqlca.sqlcode < 0) FAIL(b);
EXEC SQL UPDATE mg_msg_log
SET rc = '94', status = 'X'
WHERE stan = :h_stan AND biz_date = :h_bizdate AND msg_id <> :h_keep;
if (sqlca.sqlcode < 0) FAIL(b);
purged = (long)sqlca.sqlerrd[2];
setl(b, T_ID1, h_keep); /* 보존한 원본 msg_id */
setl(b, T_COUNT, purged);
setl(b, T_RC, (purged > 0) ? 94 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ENCODE.h - mg () MG_ENCODE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_ENCODE_H
#define MG_ENCODE_H
#include "mg_svc.h"
void MG_ENCODE(TPSVCINFO *p);
#endif /* MG_ENCODE_H */

View file

@ -0,0 +1,26 @@
/*
* MG_ENCODE.pgc - mg 모듈 서비스 MG_ENCODE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_ENCODE.h"
/* MG_ENCODE - 확장 고정길이 인코딩(순수계산): MTI(4)+처리코드(6)+금액(12)+
* STAN(6)+채널(4) 을 이어붙여 32자 프레임으로 조립하고 길이를 회신. */
void MG_ENCODE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char mti[8], proc[8], chan[16], raw[64];
long amount, stan;
gets_(b, T_STR1, mti, sizeof(mti)); if (mti[0] == 0) strcpy(mti, "0200");
gets_(b, T_STR4, proc, sizeof(proc)); if (proc[0] == 0) strcpy(proc, "000000");
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
amount = getl(b, T_AMOUNT);
stan = getl(b, T_ARG1); if (stan < 0) stan = 0;
sprintf(raw, "%.4s%.6s%012ld%06ld%.4s", mti, proc, amount, stan % 1000000, chan);
sets_(b, T_STR3, raw);
setl(b, T_COUNT, (long)strlen(raw));
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ERRCLASS.h - mg () MG_ERRCLASS (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_ERRCLASS_H
#define MG_ERRCLASS_H
#include "mg_svc.h"
void MG_ERRCLASS(TPSVCINFO *p);
#endif /* MG_ERRCLASS_H */

View file

@ -0,0 +1,30 @@
/*
* MG_ERRCLASS.pgc - mg 모듈 서비스 MG_ERRCLASS (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_ERRCLASS.h"
/* MG_ERRCLASS - 오류전문 분류: 응답코드(T_STR2)를 군으로 나눠 재처리 정책을 정한다.
* 68/91=일시오류(재시도 RETRY), 14/54=카드오류(반려 REJECT), 그 외=수동(MANUAL). */
void MG_ERRCLASS(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char rc[4], cls[16];
long policy;
gets_(b, T_STR2, rc, sizeof(rc));
if (rc[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
if (strcmp(rc, "68") == 0 || strcmp(rc, "91") == 0 || strcmp(rc, "96") == 0) {
strcpy(cls, "RETRY"); policy = 1; /* 일시오류 = 자동재시도 */
} else if (strcmp(rc, "14") == 0 || strcmp(rc, "54") == 0 || strcmp(rc, "05") == 0) {
strcpy(cls, "REJECT"); policy = 2; /* 카드/승인오류 = 반려 */
} else {
strcpy(cls, "MANUAL"); policy = 3; /* 기타 = 수동확인 */
}
sets_(b, T_STR5, cls);
setl(b, T_ARG3, policy);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ERRESCAL.h - mg () MG_ERRESCAL (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_ERRESCAL_H
#define MG_ERRESCAL_H
#include "mg_svc.h"
void MG_ERRESCAL(TPSVCINFO *p);
#endif /* MG_ERRESCAL_H */

View file

@ -0,0 +1,31 @@
/*
* MG_ERRESCAL.pgc - mg 모듈 서비스 MG_ERRESCAL (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_ERRESCAL.h"
/* MG_ERRESCAL - 재시도초과 에스컬레이션: 재시도횟수가 임계(기본5)를 넘긴 실패(F) 큐
* 건을 에스컬레이션(status='E')으로 올려 운영자 개입 대상으로 전이한다. */
void MG_ERRESCAL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long escalated;
EXEC SQL BEGIN DECLARE SECTION;
char h_bizdate[16];
long h_limit;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
h_limit = getl(b, T_ARG4); if (h_limit <= 0) h_limit = 5;
EXEC SQL UPDATE mg_queue
SET status = 'E', updated_at = now()
WHERE status = 'F' AND biz_date = :h_bizdate AND retry_cnt > :h_limit;
if (sqlca.sqlcode < 0) { userlog("MG_ERRESCAL FAIL [%d]", sqlca.sqlcode); FAIL(b); }
escalated = (long)sqlca.sqlerrd[2];
setl(b, T_COUNT, escalated);
setl(b, T_ARG4, h_limit);
setl(b, T_RC, (escalated > 0) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ERRREQUEUE.h - mg () MG_ERRREQUEUE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_ERRREQUEUE_H
#define MG_ERRREQUEUE_H
#include "mg_svc.h"
void MG_ERRREQUEUE(TPSVCINFO *p);
#endif /* MG_ERRREQUEUE_H */

View file

@ -0,0 +1,38 @@
/*
* MG_ERRREQUEUE.pgc - mg 모듈 서비스 MG_ERRREQUEUE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_ERRREQUEUE.h"
/* MG_ERRREQUEUE - 오류전문 큐 재투입: NACK(N) 상태 전문의 원 전문(raw_msg)을 다시
* 송신큐(Q)에 적재하고, 원 전문을 재처리중(P)으로 전이. 원전문 없으면 rc=25. */
void MG_ERRREQUEUE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char bizdate[16];
long qid;
EXEC SQL BEGIN DECLARE SECTION;
long h_msgid, h_stan;
char h_chan[16], h_mti[8], h_raw[512], h_status[4];
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
h_msgid = getl(b, T_ID1);
if (h_msgid <= 0) { setl(b, T_RC, 13); FAIL(b); }
EXEC SQL SELECT stan, channel, mti, raw_msg, status
INTO :h_stan, :h_chan, :h_mti, :h_raw, :h_status
FROM mg_msg_log WHERE msg_id = :h_msgid;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (strcmp(h_status, "N") != 0) { setl(b, T_RC, 1); FAIL(b); } /* NACK 아님 */
qid = mgdb_next_queue_id(); if (qid < 0) FAIL(b);
if (mgdb_enqueue(qid, h_stan, h_chan, h_mti, h_raw, bizdate) < 0) FAIL(b);
if (mgdb_update_msg_rc(h_msgid, "00", "P") < 0) FAIL(b);
setl(b, T_ID2, qid);
setl(b, T_ARG1, h_stan);
setl(b, T_RC, 0);
OK(b);
}

12
app/src/mg/svc/MG_FEP.h Normal file
View file

@ -0,0 +1,12 @@
/*
* MG_FEP.h - mg () MG_FEP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FEP_H
#define MG_FEP_H
#include "mg_svc.h"
void MG_FEP(TPSVCINFO *p);
#endif /* MG_FEP_H */

31
app/src/mg/svc/MG_FEP.pgc Normal file
View file

@ -0,0 +1,31 @@
/*
* MG_FEP.pgc - mg 모듈 서비스 MG_FEP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FEP.h"
/* MG_FEP - 프론트엔드 프로세서(FEP) 정규화 게이트: 전문 앞뒤 STX/ETX 제어문자와
* 좌우 공백을 벗겨 순수 페이로드로 정돈하고, 제거 바이트 수를 회신한다. */
void MG_FEP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], out[512];
long n, i, s, e, trimmed;
gets_(b, T_STR3, raw, sizeof(raw));
n = (long)strlen(raw);
s = 0; e = n - 1;
while (s <= e && (raw[s] == 0x02 || raw[s] == ' ')) s++; /* 앞 STX/공백 */
while (e >= s && (raw[e] == 0x03 || raw[e] == ' ')) e--; /* 뒤 ETX/공백 */
for (i = s; i <= e; i++) out[i - s] = raw[i];
out[(e >= s) ? e - s + 1 : 0] = 0;
trimmed = n - (long)strlen(out);
sets_(b, T_STR3, out);
setl(b, T_COUNT, (long)strlen(out));
setl(b, T_ARG2, trimmed);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FIELDGET.h - mg () MG_FIELDGET (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FIELDGET_H
#define MG_FIELDGET_H
#include "mg_svc.h"
void MG_FIELDGET(TPSVCINFO *p);
#endif /* MG_FIELDGET_H */

View file

@ -0,0 +1,31 @@
/*
* MG_FIELDGET.pgc - mg 모듈 서비스 MG_FIELDGET (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FIELDGET.h"
/* MG_FIELDGET - 전문 임의필드 추출(순수계산): 오프셋(T_ARG1)/길이(T_ARG2)로 지정한
* 구간을 원 전문에서 잘라 회신. 범위가 전문 길이를 벗어나면 rc=31. */
void MG_FIELDGET(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], field[256];
long off, len, n;
gets_(b, T_STR3, raw, sizeof(raw));
off = getl(b, T_ARG1);
len = getl(b, T_ARG2);
n = (long)strlen(raw);
if (off < 0 || len <= 0 || off + len > n || len >= (long)sizeof(field)) {
setl(b, T_RC, 31);
FAIL(b);
}
memcpy(field, raw + off, len);
field[len] = 0;
sets_(b, T_STR5, field);
setl(b, T_COUNT, len);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FIELDSET.h - mg () MG_FIELDSET (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FIELDSET_H
#define MG_FIELDSET_H
#include "mg_svc.h"
void MG_FIELDSET(TPSVCINFO *p);
#endif /* MG_FIELDSET_H */

View file

@ -0,0 +1,30 @@
/*
* MG_FIELDSET.pgc - mg 모듈 서비스 MG_FIELDSET (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FIELDSET.h"
/* MG_FIELDSET - 전문 임의필드 치환(순수계산): 오프셋(T_ARG1)부터 대체값(T_STR5)을
* 덮어써 전문을 갱신. 대체값이 남은 길이를 넘어가면 rc=31 로 반려한다. */
void MG_FIELDSET(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], val[256];
long off, vlen, n, i;
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_STR5, val, sizeof(val));
off = getl(b, T_ARG1);
n = (long)strlen(raw);
vlen = (long)strlen(val);
if (off < 0 || off + vlen > n) { setl(b, T_RC, 31); FAIL(b); }
for (i = 0; i < vlen; i++) raw[off + i] = val[i]; /* 제자리 덮어쓰기 */
sets_(b, T_STR3, raw);
setl(b, T_COUNT, vlen);
setl(b, T_ARG2, off + vlen); /* 다음 필드 시작 오프셋 */
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FINADV.h - mg () MG_FINADV (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FINADV_H
#define MG_FINADV_H
#include "mg_svc.h"
void MG_FINADV(TPSVCINFO *p);
#endif /* MG_FINADV_H */

View file

@ -0,0 +1,36 @@
/*
* MG_FINADV.pgc - mg 모듈 서비스 MG_FINADV (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FINADV.h"
/* MG_FINADV - 0220 금융통지 수신: 온라인 승인 없이 오프라인 거래를 사후 통지하는
* 전문을 원장에 0220/IN/통지수신(A)으로 적재. 처리코드 앞2자리를 거래구분으로 회신. */
void MG_FINADV(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], proc[8], txntype[4], bizdate[16];
long stan, msg_id, amount, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(proc, 0, sizeof(proc));
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
amount = (n >= 22) ? atol(raw + 10) : getl(b, T_AMOUNT);
memset(txntype, 0, sizeof(txntype));
strncpy(txntype, proc, 2); /* 처리코드 상위 2자리 = 거래구분 */
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0220", chan, "IN", proc, "", amount,
"", "00", raw, "A", bizdate) < 0) FAIL(b);
sets_(b, T_STR4, txntype);
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FINADVRSP.h - mg () MG_FINADVRSP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FINADVRSP_H
#define MG_FINADVRSP_H
#include "mg_svc.h"
void MG_FINADVRSP(TPSVCINFO *p);
#endif /* MG_FINADVRSP_H */

View file

@ -0,0 +1,36 @@
/*
* MG_FINADVRSP.pgc - mg 모듈 서비스 MG_FINADVRSP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FINADVRSP.h"
/* MG_FINADVRSP - 0230 금융통지응답 생성: 원 0220 통지를 STAN 으로 조회해 응답전문
* 0230 을 OUT 으로 채번·적재하고 원 통지를 확인완료(C)로 마감. 원 통지 없으면 rc=25. */
void MG_FINADVRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], curstat[4];
long orig_stan, orig_id, stan, msg_id;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
orig_stan = getl(b, T_ARG1);
if (orig_stan <= 0) { setl(b, T_RC, 13); FAIL(b); }
orig_id = mgdb_find_msg_by_stan(orig_stan);
if (orig_id < 0) { setl(b, T_RC, 25); FAIL(b); }
curstat[0] = 0;
if (mgdb_get_msg_status(orig_id, curstat) < 0) FAIL(b);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0230", chan, "OUT", "000000", "", 0,
"", "00", "", "S", bizdate) < 0) FAIL(b);
if (mgdb_update_msg_rc(orig_id, "00", "C") < 0) FAIL(b);
sets_(b, T_STR5, curstat); /* 마감 직전 상태 */
setl(b, T_ID1, msg_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FINREQ.h - mg () MG_FINREQ (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FINREQ_H
#define MG_FINREQ_H
#include "mg_svc.h"
void MG_FINREQ(TPSVCINFO *p);
#endif /* MG_FINREQ_H */

View file

@ -0,0 +1,41 @@
/*
* MG_FINREQ.pgc - mg 모듈 서비스 MG_FINREQ (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FINREQ.h"
/* MG_FINREQ - 0200 금융요청 수신: 고정길이(MTI/처리코드/금액/PAN/할부개월) 파싱,
* PAN Luhn 검증 후 원장 적재. 할부개월이 있으면 회차금액(month_amount)도 산출. */
void MG_FINREQ(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], bizdate[16], proc[8], pan[24], rc[4], months_s[4];
long stan, msg_id, amount, months, month_amt, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(proc, 0, sizeof(proc)); memset(pan, 0, sizeof(pan)); memset(months_s, 0, sizeof(months_s));
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
amount = (n >= 22) ? atol(raw + 10) : getl(b, T_AMOUNT);
if (n >= 38) strncpy(pan, raw + 22, 16);
if (n >= 40) strncpy(months_s, raw + 38, 2);
months = atol(months_s);
if (months < 1) months = 1;
month_amt = amount / months;
strcpy(rc, (pan[0] && !acq_luhn_ok(pan)) ? "14" : "00");
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0200", chan, "IN", proc, "", amount,
"", rc, raw, "R", bizdate) < 0) FAIL(b);
sets_(b, T_STR2, rc);
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
setl(b, T_INSTALL_N, months);
setl(b, T_AMT1, month_amt);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_FINRSP.h - mg () MG_FINRSP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_FINRSP_H
#define MG_FINRSP_H
#include "mg_svc.h"
void MG_FINRSP(TPSVCINFO *p);
#endif /* MG_FINRSP_H */

View file

@ -0,0 +1,37 @@
/*
* MG_FINRSP.pgc - mg 모듈 서비스 MG_FINRSP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_FINRSP.h"
/* MG_FINRSP - 0210 금융응답 생성: 승인여부(T_ARG2!=0=승인)를 응답코드로 매핑하여
* 0210 응답전문을 OUT 으로 채번·적재하고, 원 0200 요청의 응답코드/상태를 갱신. */
void MG_FINRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], rc[4];
long orig_stan, orig_id, stan, msg_id, amount, approved;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
amount = getl(b, T_AMOUNT);
approved = getl(b, T_ARG2);
orig_stan = getl(b, T_ARG1);
strcpy(rc, approved ? "00" : "05"); /* 05 = 승인거절 */
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0210", chan, "OUT", "000000", "", amount,
"", rc, "", "S", bizdate) < 0) FAIL(b);
if (orig_stan > 0) {
orig_id = mgdb_find_msg_by_stan(orig_stan);
if (orig_id >= 0 && mgdb_update_msg_rc(orig_id, rc, "S") < 0) FAIL(b);
}
sets_(b, T_STR2, rc);
setl(b, T_ID1, msg_id);
setl(b, T_RC, approved ? 0 : 5);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_HOSTADPT.h - mg () MG_HOSTADPT (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_HOSTADPT_H
#define MG_HOSTADPT_H
#include "mg_svc.h"
void MG_HOSTADPT(TPSVCINFO *p);
#endif /* MG_HOSTADPT_H */

View file

@ -0,0 +1,29 @@
/*
* MG_HOSTADPT.pgc - mg 모듈 서비스 MG_HOSTADPT (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_HOSTADPT.h"
/* MG_HOSTADPT - 매입사 호스트 직연동 어댑터: 내부 고정길이 전문 뒤에 라우팅 조회로
* 구한 목적지 노드ID 를 부착한 호스트 프레임 "<전문>@<dest>" 를 만든다. */
void MG_HOSTADPT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], mti[8], raw[512], dest[64], frame[600];
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02");
gets_(b, T_STR1, mti, sizeof(mti)); if (mti[0] == 0) strcpy(mti, "0200");
gets_(b, T_STR3, raw, sizeof(raw));
if (mgdb_lookup_route(chan, mti, dest) < 0) {
userlog("MG_HOSTADPT 목적지 미상 chan=%s mti=%s", chan, mti);
strcpy(dest, "DEFAULT"); /* 라우팅 없으면 기본 호스트 */
}
snprintf(frame, sizeof(frame), "%s@%s", raw, dest);
sets_(b, T_STR3, frame);
sets_(b, T_STR5, dest);
setl(b, T_COUNT, (long)strlen(frame));
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_INFLIGHT.h - mg () MG_INFLIGHT (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_INFLIGHT_H
#define MG_INFLIGHT_H
#include "mg_svc.h"
void MG_INFLIGHT(TPSVCINFO *p);
#endif /* MG_INFLIGHT_H */

View file

@ -0,0 +1,31 @@
/*
* MG_INFLIGHT.pgc - mg 모듈 서비스 MG_INFLIGHT (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_INFLIGHT.h"
/* MG_INFLIGHT - 미완료(in-flight) 전문 집계: 아직 응답이 확정되지 않은 수신(R)+
* 송신중(S) 상태 전문 수를 채널별로 세어, 임계(기본500) 초과 시 rc=1 경보. */
void MG_INFLIGHT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long limit;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_bizdate[16];
long h_cnt;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
limit = getl(b, T_ARG4); if (limit <= 0) limit = 500;
EXEC SQL SELECT count(*) INTO :h_cnt
FROM mg_msg_log
WHERE channel = :h_chan AND biz_date = :h_bizdate AND status IN ('R', 'S');
if (sqlca.sqlcode < 0) FAIL(b);
setl(b, T_COUNT, h_cnt);
setl(b, T_ARG4, limit);
setl(b, T_RC, (h_cnt > limit) ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_INQRSP.h - mg () MG_INQRSP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_INQRSP_H
#define MG_INQRSP_H
#include "mg_svc.h"
void MG_INQRSP(TPSVCINFO *p);
#endif /* MG_INQRSP_H */

View file

@ -0,0 +1,36 @@
/*
* MG_INQRSP.pgc - mg 모듈 서비스 MG_INQRSP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_INQRSP.h"
/* MG_INQRSP - 0110 조회응답 생성: 조회 잔액(T_AMT1)을 12자리 우측정렬로 응답전문
* 0110 필드52에 채워 OUT 으로 채번·적재하고, 원 0100 조회요청을 완료(C)로 전이. */
void MG_INQRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], bizdate[16], iso[64];
long orig_stan, orig_id, stan, msg_id, balance;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
balance = getl(b, T_AMT1);
orig_stan = getl(b, T_ARG1);
sprintf(iso, "0110%012ld", balance); /* 필드52: 조회잔액 12자리 */
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0110", chan, "OUT", "310000", "", balance,
"", "00", iso, "S", bizdate) < 0) FAIL(b);
if (orig_stan > 0) {
orig_id = mgdb_find_msg_by_stan(orig_stan);
if (orig_id >= 0 && mgdb_update_msg_rc(orig_id, "00", "C") < 0) FAIL(b);
}
sets_(b, T_STR3, iso);
setl(b, T_ID1, msg_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_ISOADPT.h - mg () MG_ISOADPT (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_ISOADPT_H
#define MG_ISOADPT_H
#include "mg_svc.h"
void MG_ISOADPT(TPSVCINFO *p);
#endif /* MG_ISOADPT_H */

View file

@ -0,0 +1,43 @@
/*
* MG_ISOADPT.pgc - mg 모듈 서비스 MG_ISOADPT (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_ISOADPT.h"
/* ----- service-local pure helper(s) ----- */
/* ISO8583 MTI 유효성: 4자리 숫자이면서 첫자리(버전)가 0~2 인지 검사. */
static int mg_iso_mti_ok(const char *mti)
{
int i;
if (strlen(mti) < 4) return 0;
for (i = 0; i < 4; i++) if (mti[i] < '0' || mti[i] > '9') return 0;
return (mti[0] >= '0' && mti[0] <= '2');
}
/* MG_ISOADPT - 표준 ISO8583 직연동 어댑터: 원 전문의 MTI(선두4)를 표준규격으로
* 검증하고 클래스(승인02/금융02·통지 등)를 판별해 회신. 규격위반 시 rc=30. */
void MG_ISOADPT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], mti[8], cls[16];
gets_(b, T_STR3, raw, sizeof(raw));
memset(mti, 0, sizeof(mti));
strncpy(mti, raw, 4);
if (!mg_iso_mti_ok(mti)) { setl(b, T_RC, 30); FAIL(b); }
switch (mti[1]) { /* 메시지 클래스(둘째자리) */
case '1': strcpy(cls, "AUTH"); break; /* 1xxx 승인 */
case '2': strcpy(cls, "FINANCE");break; /* 2xxx 금융 */
case '4': strcpy(cls, "REVERSE");break; /* 4xxx 취소 */
case '8': strcpy(cls, "NETMGMT");break; /* 8xxx 망관리 */
default: strcpy(cls, "OTHER"); break;
}
sets_(b, T_STR1, mti);
sets_(b, T_STR5, cls);
sets_(b, T_CHANNEL, "CH03");
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_KEYCHECK.h - mg () MG_KEYCHECK (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_KEYCHECK_H
#define MG_KEYCHECK_H
#include "mg_svc.h"
void MG_KEYCHECK(TPSVCINFO *p);
#endif /* MG_KEYCHECK_H */

View file

@ -0,0 +1,49 @@
/*
* MG_KEYCHECK.pgc - mg 모듈 서비스 MG_KEYCHECK (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_KEYCHECK.h"
/* ----- service-local pure helper(s) ----- */
/* 키 문자열의 체크값(KCV): 바이트 합을 세대(keyver)로 섞어 6-hex 로 산출. */
static void mg_keycheck_kcv(const char *key, long keyver, char *out /* >= 7 */)
{
unsigned long acc = (unsigned long)(keyver * 2654435761UL);
int i;
for (i = 0; key[i]; i++) acc += (unsigned char)key[i] * (unsigned long)(i + 1);
sprintf(out, "%06lX", acc & 0xFFFFFFUL);
}
/* MG_KEYCHECK - 키 체크값(KCV) 계산·대조: 채널 세션키로 KCV 를 만들어 입력 기대값
* (T_KEY2)과 비교. 기대값 미제공이면 계산만 회신, 불일치면 rc=1(키 불일치). */
void MG_KEYCHECK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char expect[16], kcv[12];
long keyver, mismatch = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_sk[64];
long h_kv;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_KEY2, expect, sizeof(expect));
h_sk[0] = 0;
EXEC SQL SELECT session_key INTO :h_sk FROM mg_channel WHERE channel = :h_chan;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (h_sk[0] == 0) { setl(b, T_RC, 1); FAIL(b); }
h_kv = 0;
EXEC SQL SELECT seq_val INTO :h_kv FROM mg_seq WHERE seq_name = 'KEYVER';
keyver = (sqlca.sqlcode == 100) ? 0 : h_kv;
mg_keycheck_kcv(h_sk, keyver, kcv);
if (expect[0] && strcmp(expect, kcv) != 0) mismatch = 1;
sets_(b, T_KEY1, kcv);
setl(b, T_ARG3, keyver);
setl(b, T_RC, mismatch ? 1 : 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_KEYEXPIRE.h - mg () MG_KEYEXPIRE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_KEYEXPIRE_H
#define MG_KEYEXPIRE_H
#include "mg_svc.h"
void MG_KEYEXPIRE(TPSVCINFO *p);
#endif /* MG_KEYEXPIRE_H */

View file

@ -0,0 +1,33 @@
/*
* MG_KEYEXPIRE.pgc - mg 모듈 서비스 MG_KEYEXPIRE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_KEYEXPIRE.h"
/* MG_KEYEXPIRE - 키 만료 판정: 현재 KEYVER 세대와 전문에 실린 사용세대(T_ARG3)의
* 차이가 허용범위(기본 1세대)를 넘으면 만료(rc=1)로 판정해 재키교환을 유도. */
void MG_KEYEXPIRE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long used_ver, allow, drift, expired;
EXEC SQL BEGIN DECLARE SECTION;
long h_cur;
EXEC SQL END DECLARE SECTION;
used_ver = getl(b, T_ARG3);
allow = getl(b, T_ARG4); if (allow <= 0) allow = 1;
h_cur = 0;
EXEC SQL SELECT seq_val INTO :h_cur FROM mg_seq WHERE seq_name = 'KEYVER';
if (sqlca.sqlcode < 0) FAIL(b);
drift = h_cur - used_ver;
expired = (drift > allow) ? 1 : 0;
if (expired)
userlog("MG_KEYEXPIRE 키만료 현재세대=%ld 사용세대=%ld 허용=%ld", h_cur, used_ver, allow);
setl(b, T_ARG1, h_cur);
setl(b, T_ARG6, drift);
setl(b, T_RC, expired);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_KEYLOAD.h - mg () MG_KEYLOAD (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_KEYLOAD_H
#define MG_KEYLOAD_H
#include "mg_svc.h"
void MG_KEYLOAD(TPSVCINFO *p);
#endif /* MG_KEYLOAD_H */

View file

@ -0,0 +1,41 @@
/*
* MG_KEYLOAD.pgc - mg 모듈 서비스 MG_KEYLOAD (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_KEYLOAD.h"
/* MG_KEYLOAD - 초기 키 적재(KEK 주입): KEYVER 카운터를 1로 초기 승번하고 대상 채널에
* 최초 세션키 "K0001-초기STAN" 을 발급, 채널을 SIGNON 준비상태로 만든다. */
void MG_KEYLOAD(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], newkey[24];
long stan;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16];
long h_kv;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0;
/* KEYVER 를 1 로 (재)초기화 (초기 키적재는 세대 리셋) */
EXEC SQL UPDATE mg_seq SET seq_val = 1, updated_at = now() WHERE seq_name = 'KEYVER';
if (sqlca.sqlcode < 0) FAIL(b);
if (sqlca.sqlerrd[2] == 0) {
EXEC SQL INSERT INTO mg_seq (seq_name, seq_val) VALUES ('KEYVER', 1);
if (sqlca.sqlcode < 0) FAIL(b);
}
EXEC SQL SELECT seq_val INTO :h_kv FROM mg_seq WHERE seq_name = 'KEYVER';
if (sqlca.sqlcode < 0) FAIL(b);
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
sprintf(newkey, "K%04ld-%04ld", h_kv, stan % 10000);
if (mgdb_set_channel_session(chan, newkey, stan) < 0) FAIL(b);
userlog("MG_KEYLOAD 초기키 적재 채널=%s 키=%s", chan, newkey);
sets_(b, T_KEY1, newkey);
setl(b, T_ARG3, h_kv);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_LOADBAL.h - mg () MG_LOADBAL (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_LOADBAL_H
#define MG_LOADBAL_H
#include "mg_svc.h"
void MG_LOADBAL(TPSVCINFO *p);
#endif /* MG_LOADBAL_H */

View file

@ -0,0 +1,44 @@
/*
* MG_LOADBAL.pgc - mg 모듈 서비스 MG_LOADBAL (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_LOADBAL.h"
/* MG_LOADBAL - 채널 부하분산 선택: UP 상태 채널들 중 당일 처리건수가 가장 적은
* 채널을 골라 다음 전문의 송신 채널로 회신한다. UP 채널이 없으면 rc=1. */
void MG_LOADBAL(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char best[16];
long best_load = -1, considered = 0;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_bizdate[16];
long h_cnt;
EXEC SQL END DECLARE SECTION;
gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
best[0] = 0;
EXEC SQL DECLARE lbc CURSOR FOR
SELECT channel FROM mg_channel WHERE status = 'UP' ORDER BY channel;
EXEC SQL OPEN lbc;
if (sqlca.sqlcode < 0) FAIL(b);
for (;;) {
EXEC SQL FETCH lbc INTO :h_chan;
if (sqlca.sqlcode == 100) break;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lbc; FAIL(b); }
considered++;
EXEC SQL SELECT count(*) INTO :h_cnt
FROM mg_msg_log WHERE channel = :h_chan AND biz_date = :h_bizdate;
if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lbc; FAIL(b); }
if (best_load < 0 || h_cnt < best_load) { best_load = h_cnt; strcpy(best, h_chan); }
}
EXEC SQL CLOSE lbc;
if (best[0] == 0) { setl(b, T_RC, 1); FAIL(b); }
sets_(b, T_CHANNEL, best);
setl(b, T_COUNT, best_load);
setl(b, T_ARG3, considered);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_MACSESSION.h - mg () MG_MACSESSION (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_MACSESSION_H
#define MG_MACSESSION_H
#include "mg_svc.h"
void MG_MACSESSION(TPSVCINFO *p);
#endif /* MG_MACSESSION_H */

View file

@ -0,0 +1,46 @@
/*
* MG_MACSESSION.pgc - mg 모듈 서비스 MG_MACSESSION (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_MACSESSION.h"
/* ----- service-local pure helper(s) ----- */
/* 세션키 문자열과 전문을 합성 CBC-MAC 흉내로 섞어 8-hex MAC 산출. */
static void mg_macsession_calc(const char *sk, const char *msg, char *out /* >= 9 */)
{
unsigned long acc = 0;
int i;
for (i = 0; sk[i]; i++) acc = acc * 131UL + (unsigned char)sk[i]; /* 키 프리믹스 */
for (i = 0; msg[i]; i++) {
acc ^= (unsigned char)msg[i];
acc = (acc << 3) | (acc >> 29); /* 3비트 좌회전(체이닝) */
}
sprintf(out, "%08lX", acc & 0xFFFFFFFFUL);
}
/* MG_MACSESSION - 세션키 기반 MAC 생성: 채널의 현재 세션키(mg_channel.session_key)를
* 읽어 전문과 합성한 세션MAC 을 회신. 세션키 미설정(사인온 전) 채널이면 rc=1. */
void MG_MACSESSION(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char raw[512], mac[12];
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16], h_sk[64];
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
if (raw[0] == 0) { setl(b, T_RC, 30); FAIL(b); }
h_sk[0] = 0;
EXEC SQL SELECT session_key INTO :h_sk FROM mg_channel WHERE channel = :h_chan;
if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); }
if (sqlca.sqlcode < 0) FAIL(b);
if (h_sk[0] == 0) { setl(b, T_RC, 1); FAIL(b); } /* 세션키 없음 */
mg_macsession_calc(h_sk, raw, mac);
sets_(b, T_KEY1, mac);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_NMADVICE.h - mg () MG_NMADVICE (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_NMADVICE_H
#define MG_NMADVICE_H
#include "mg_svc.h"
void MG_NMADVICE(TPSVCINFO *p);
#endif /* MG_NMADVICE_H */

View file

@ -0,0 +1,40 @@
/*
* MG_NMADVICE.pgc - mg 모듈 서비스 MG_NMADVICE (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_NMADVICE.h"
/* MG_NMADVICE - 0820 망관리통지 처리: 키교환(NMIC 101)/컷오버(201) 등 무응답 통지를
* 원장에 0820/IN 으로 적재. NMIC 101 이면 세션키 재발급을 함께 트리거한다. */
void MG_NMADVICE(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], nmic[8], bizdate[16], newkey[24];
long stan, msg_id, n, kv = 0;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(nmic, 0, sizeof(nmic));
if (n >= 7) strncpy(nmic, raw + 4, 3); else strcpy(nmic, "201");
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0820", chan, "IN", nmic, "", 0,
"", "00", raw, "A", bizdate) < 0) FAIL(b);
if (strcmp(nmic, "101") == 0) { /* 키교환 통지 -> 세션키 재발급 */
kv = mgdb_seq_bump("KEYVER"); if (kv < 0) FAIL(b);
sprintf(newkey, "K%04ld-%04ld", kv, stan % 10000);
if (mgdb_set_channel_session(chan, newkey, stan) < 0) FAIL(b);
sets_(b, T_KEY1, newkey);
}
sets_(b, T_STR4, nmic);
setl(b, T_ID1, msg_id);
setl(b, T_ARG3, kv);
setl(b, T_RC, 0);
OK(b);
}

12
app/src/mg/svc/MG_NMRSP.h Normal file
View file

@ -0,0 +1,12 @@
/*
* MG_NMRSP.h - mg () MG_NMRSP (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_NMRSP_H
#define MG_NMRSP_H
#include "mg_svc.h"
void MG_NMRSP(TPSVCINFO *p);
#endif /* MG_NMRSP_H */

View file

@ -0,0 +1,32 @@
/*
* MG_NMRSP.pgc - mg 모듈 서비스 MG_NMRSP (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_NMRSP.h"
/* MG_NMRSP - 0810 망관리응답 생성: 수신한 0800 망관리요청의 네트워크정보코드(NMIC,
* 처리코드 상위3자리)를 그대로 되받아 0810 응답전문을 OUT 으로 채번·적재. */
void MG_NMRSP(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], nmic[8], bizdate[16];
long stan, msg_id, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(nmic, 0, sizeof(nmic));
if (n >= 7) strncpy(nmic, raw + 4, 3); else strcpy(nmic, "001"); /* 001=로그온 */
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0810", chan, "OUT", nmic, "", 0,
"", "00", raw, "S", bizdate) < 0) FAIL(b);
sets_(b, T_STR4, nmic);
setl(b, T_ID1, msg_id);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_PINBLOCK.h - mg () MG_PINBLOCK (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_PINBLOCK_H
#define MG_PINBLOCK_H
#include "mg_svc.h"
void MG_PINBLOCK(TPSVCINFO *p);
#endif /* MG_PINBLOCK_H */

View file

@ -0,0 +1,47 @@
/*
* MG_PINBLOCK.pgc - mg 모듈 서비스 MG_PINBLOCK (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_PINBLOCK.h"
/* ----- service-local pure helper(s) ----- */
/* ISO-0 PIN 블록: PIN(0x0N + PIN + F패딩) XOR PAN(0000 + PAN뒤12중앙) -> 16-hex. */
static void mg_pinblock_iso0(const char *pin, const char *pan, char *out /* >= 17 */)
{
unsigned char pinb[8], panb[8], i;
char t[17];
int plen = (int)strlen(pin), panlen = (int)strlen(pan);
/* PIN 필드 */
sprintf(t, "0%X%-14.14s", plen, pin);
for (i = 0; i < 14; i++) if (t[2 + i] == ' ') t[2 + i] = 'F';
for (i = 0; i < 8; i++) { char h[3] = { t[i*2], t[i*2+1], 0 }; pinb[i] = (unsigned char)strtol(h, NULL, 16); }
/* PAN 필드: 0000 + PAN(체크자리 제외 우측 12) */
memset(panb, 0, sizeof(panb));
if (panlen >= 13) {
char pp[13]; memcpy(pp, pan + (panlen - 13), 12); pp[12] = 0;
for (i = 0; i < 6; i++) { char h[3] = { pp[i*2], pp[i*2+1], 0 }; panb[2 + i] = (unsigned char)strtol(h, NULL, 16); }
}
for (i = 0; i < 8; i++) sprintf(out + i*2, "%02X", pinb[i] ^ panb[i]);
}
/* MG_PINBLOCK - ISO-0 PIN 블록 조립(순수계산): PIN(T_KEY1)과 PAN(T_STR5)으로
* 포맷0 PIN 블록 16-hex 를 만들어 회신. PIN 길이 4~12 아니면 rc=30. */
void MG_PINBLOCK(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char pin[16], pan[24], block[20];
long plen;
gets_(b, T_KEY1, pin, sizeof(pin));
gets_(b, T_STR5, pan, sizeof(pan));
plen = (long)strlen(pin);
if (plen < 4 || plen > 12) { setl(b, T_RC, 30); FAIL(b); }
mg_pinblock_iso0(pin, pan, block);
sets_(b, T_KEY2, block);
setl(b, T_COUNT, plen);
setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_PREAUTH.h - mg () MG_PREAUTH (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_PREAUTH_H
#define MG_PREAUTH_H
#include "mg_svc.h"
void MG_PREAUTH(TPSVCINFO *p);
#endif /* MG_PREAUTH_H */

View file

@ -0,0 +1,37 @@
/*
* MG_PREAUTH.pgc - mg 모듈 서비스 MG_PREAUTH (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_PREAUTH.h"
/* MG_PREAUTH - 0100 예비승인(hold): 요청금액에 홀드율(기본110%)을 적용한 승인예정액을
* 산출해 원장에 0100/IN/status='H'(보류) 로 적재. 홀드율은 T_ARG4(bps)로 조정 가능. */
void MG_PREAUTH(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
char chan[16], raw[512], bizdate[16], proc[8];
long stan, msg_id, amount, hold_bps, hold_amt, n;
gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01");
gets_(b, T_STR3, raw, sizeof(raw));
gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
n = (long)strlen(raw);
memset(proc, 0, sizeof(proc));
if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000");
amount = (n >= 22) ? atol(raw + 10) : getl(b, T_AMOUNT);
hold_bps = getl(b, T_ARG4); if (hold_bps <= 0) hold_bps = 11000; /* 110% */
hold_amt = amount * hold_bps / 10000;
stan = mgdb_next_stan(); if (stan < 0) FAIL(b);
msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b);
if (mgdb_insert_msg(msg_id, stan, "0100", chan, "IN", proc, "", hold_amt,
"", "00", raw, "H", bizdate) < 0) FAIL(b);
setl(b, T_AMT1, hold_amt);
setl(b, T_ARG4, hold_bps);
setl(b, T_ID1, msg_id);
setl(b, T_ARG1, stan);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_RATELIMIT.h - mg () MG_RATELIMIT (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_RATELIMIT_H
#define MG_RATELIMIT_H
#include "mg_svc.h"
void MG_RATELIMIT(TPSVCINFO *p);
#endif /* MG_RATELIMIT_H */

View file

@ -0,0 +1,37 @@
/*
* MG_RATELIMIT.pgc - mg 모듈 서비스 MG_RATELIMIT (one service = one file).
* 신규 스케일아웃 서비스; 캘러의 XA 브랜치에서 실행. mg_svr 가 광고.
*/
#include "MG_RATELIMIT.h"
/* MG_RATELIMIT - 초당 유입 레이트리밋: 채널의 최근 1초간 수신 전문 수를 세어
* 초당한도(TPS, 기본50) 초과면 차단(rc=91), 80% 이상이면 경보(rc=1)로 판정. */
void MG_RATELIMIT(TPSVCINFO *p)
{
UBFH *b = (UBFH *)p->data;
long tps, pct;
EXEC SQL BEGIN DECLARE SECTION;
char h_chan[16];
long h_cnt, h_tps;
EXEC SQL END DECLARE SECTION;
gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01");
tps = getl(b, T_ARG4); if (tps <= 0) tps = 50;
h_tps = tps;
EXEC SQL SELECT count(*) INTO :h_cnt
FROM mg_msg_log
WHERE channel = :h_chan AND direction = 'IN'
AND created_at >= now() - interval '1 second';
if (sqlca.sqlcode < 0) FAIL(b);
pct = (h_cnt * 100L) / h_tps;
setl(b, T_COUNT, h_cnt);
setl(b, T_ARG4, h_tps);
setl(b, T_ARG8, pct);
if (h_cnt > h_tps) setl(b, T_RC, 91);
else if (pct >= 80) setl(b, T_RC, 1);
else setl(b, T_RC, 0);
OK(b);
}

View file

@ -0,0 +1,12 @@
/*
* MG_READYZ.h - mg () MG_READYZ (copybook / record header).
* ; mg_svr tpsvrinit .
*/
#ifndef MG_READYZ_H
#define MG_READYZ_H
#include "mg_svc.h"
void MG_READYZ(TPSVCINFO *p);
#endif /* MG_READYZ_H */

Some files were not shown because too many files have changed in this diff Show more