제안요약서 slide15 현행진단이 "보관 기간 — 2년 이상, 기본값 730일, 관리자 설정값 미존재 → 신규 구축"으로 지목한 항목이다. 파기 배치가 자바 상수 ACCESS_LOG_RETENTION_DAYS=730 을 써서 관리자가 바꿀 수 없었다. · ACCOUNT_MNG_POLICY.ACCESS_LOG_KEEP_DAY 신설(기본 730) 이 표에 이미 LOGIN_HIST_KEEP_DAY 가 있어 사실상 보관·파기 정책의 단일 창구다. 표를 새로 만들면 관리자가 보관기간을 두 화면에서 나눠 보게 된다. · 법정 하한 730일을 세 겹으로 강제 화면 JS / 서버 validatePolicy / 파기 배치. 화면만 막으면 요청을 직접 만드는 경로가, 저장만 막으면 정책 행을 직접 고치는 경로가 남는다. 배치는 정책값이 730 미만이어도 730일치를 남긴다 — 한 번 지운 접속기록은 되돌릴 수 없어 설정 실수로 법정 보관기간을 깨는 쪽보다 더 남기는 쪽으로 기운다. DB CHECK 를 쓰지 않은 이유는 MariaDB/MySQL 5.7 에서 동작이 갈려 조용히 통과할 수 있어서다. · 접속기록 조회 화면의 "보관기간 N일" 안내도 정책값을 읽는다 파기 배치가 실제로 쓰는 값과 달라지면 안내가 거짓말이 된다. 정책 조회 실패 시 양쪽 모두 기본값으로 물러난다(안내 때문에 목록이 막히면 안 된다). 검증: mvn compile EXIT=0, JSP Jasper 오류 0건, DDL 적용 후 ACCESS_LOG_KEEP_DAY=730 확인, 재기동 정상, 정책화면·접속기록 302→로그인. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1824 lines
125 KiB
SQL
1824 lines
125 KiB
SQL
-- #####################################################################
|
||
-- arko_renual 설치용 통합 SQL (개인정보보호 기능개선 사업 · arko 대비 변동분)
|
||
-- 대상 DB : MySQL 5.7 / MariaDB (스키마 csv)
|
||
-- 구성 : [PART 1] DDL(테이블 생성·변경, 25개 테이블/인덱스 + 정책 컬럼)
|
||
-- → [PART 2] DML(기준/시드 데이터)
|
||
-- → [PART 3] 관리자 메뉴 등록 DML(좌측메뉴 노출, COMTNPROGRMLIST/
|
||
-- COMTNMENUINFO/COMTNMENUCREATDTLS,
|
||
-- 9100035~9100039 + 9100044 + 9100047 + 9100051)
|
||
-- ※ 접속·처리기록 영역의 화면은 9100044 "접속기록 조회 및 감사" 하나다
|
||
-- (RFP 과업내용 다 = "관리자용 로그 조회 화면" 단수, 시안도 한 장).
|
||
-- 처리기록·이상행위·파기이력은 그 화면 안에서 제공한다.
|
||
-- ※ 9100045(업무유형 매핑 관리)·9100046(로그인 접속기록)은 2026-08-11 폐기
|
||
-- — 폐기된 번호는 재사용하지 않는다(과거 권한매핑 잔재와 섞인다)
|
||
-- → [PART 4] 뷰 GNR 컷오버(comvnusermaster/comvnuserinfo, 회원 이름 암호화
|
||
-- 최종 단계) — **자체 게이트 내장**, 백필 미완료 시 자동 SKIP(PART 4 헤더 참조)
|
||
-- 실행순서: 반드시 PART 1 → PART 2 → PART 3 → PART 4 순서로 실행
|
||
-- (테이블 생성 → 시드 적재 → 메뉴 등록 → 뷰 컷오버. 메뉴는 FK로 프로그램
|
||
-- 등록을 먼저 요구하고, PART 4는 PART 1의 COMTNGNRLMBER 컬럼 추가를 전제한다)
|
||
-- 재실행 : **전 구간 재실행 안전(멱등)** — PART 1 은 CREATE TABLE IF NOT EXISTS 와
|
||
-- information_schema 존재 확인 후 실행되는 ALTER(컬럼/인덱스), PART 2·3 은
|
||
-- NOT EXISTS 가드, PART 4 는 자체 게이트(이미 적용됨/백필 미완료 시 SKIP)를 쓴다.
|
||
-- 이미 적용된 항목은 'SKIP: ...' 메시지만 출력하고 넘어가므로, 기존 환경에도
|
||
-- 이 파일 하나만 그대로 실행하면 된다(PART 4는 백필 완료 후 재실행 1회 필요할 수 있음).
|
||
-- (2026-08-05 이전 버전은 재실행 시 1050/1060/1061 오류가 났다)
|
||
-- ★★ PART 3 적용만으로는 좌측메뉴에 안 뜬다 — left.jsp 하드코딩 <c:when> 분기가
|
||
-- 반드시 함께 배포돼야 한다(소스에는 이미 반영됨). 상세는 PART 3 헤더 참조. ★★
|
||
-- ★★ PART 4(뷰 컷오버) 전에 회원 이름 백필 배치가 끝나 있어야 한다 —
|
||
-- /mnt/sec/memberNmCrypto/start.do?confirm=BACKFILL(POST, ROLE_ADMIN, 2026-08-11 신설
|
||
-- MemberNmCryptoController). 안 끝났으면 PART 4는 조용히 SKIP만 하고 넘어간다(에러 아님). ★★
|
||
-- 2026-08-11 개정: DDL 6건(access_control_policy/download_job/emplyr_scrty_index/
|
||
-- mber_nm_encrypt_create/mber_nm_dead_views_drop 5건→PART 1, mber_nm_view_hash_cutover
|
||
-- 1건→신설 PART 4) + 메뉴 1건(access_policy 9100047→PART 3)을 통합해 이 파일 하나가
|
||
-- 다시 완전한 정본이 되도록 했다(그 전까지 DDL 27건 중 19건만 커버하던 drift 해소).
|
||
-- `backup/privacy.mber_nm_backup_mysql.sql`(평문 백업)·
|
||
-- `backup/privacy.mber_nm_dead_views_backup_20260810.sql`(죽은 뷰 정의 백업)은
|
||
-- 되돌리기용 스냅샷 성격이라 이 통합 파일 실행 **전**에 별도로 실행하는 것을 권장하며
|
||
-- 통합하지 않았다(설치 로직이 아니라 사고 대비 아카이브라 실행 시점이 다름).
|
||
-- 2026-08-27 개정(운영 실측 대조 반영):
|
||
-- [운영 실측 2026-08-27 15:31] @@version=5.7.31-log (**MySQL 5.7, MariaDB 아님**),
|
||
-- @@lower_case_table_names=**0**(대소문자 구분),
|
||
-- @@sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,
|
||
-- ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
|
||
-- (로컬 8.0.46 은 lctn=1, sql_mode 는 사실상 동일 → sql_mode 는 위험요소 아님)
|
||
-- 운영 DDL 덤프(csv_structure_Dump20260520) ↔ 로컬 csv 전수 대조 결과를 반영했다.
|
||
-- ① [치명] 운영은 객체명이 **대문자**(`COMTNGNRLMBER`), 로컬은 소문자다. PART 4 의
|
||
-- REPLACE() 는 lower_case_table_names 와 무관하게 항상 대소문자를 구분하므로,
|
||
-- 종전의 소문자 하드코딩은 운영에서 5개 치환이 전부 불발되고도
|
||
-- CREATE OR REPLACE VIEW 가 **에러 없이 성공**해 USER_NM_HASH 없는 뷰를 남겼다.
|
||
-- → information_schema 에서 실제 저장된 케이스를 읽어 검색문자열을 조립하고,
|
||
-- 치환 결과가 기대(USER_NM_HASH 4회 + MBER_NM_ENC + 평문참조 0)와 다르면
|
||
-- SIGNAL SQLSTATE '45000' 으로 **중단**하는 하드 게이트를 추가했다.
|
||
-- ② PART 1 의 죽은 뷰 8개 DROP 도 같은 이유로 운영에서 조용히 실패했다(운영 실제명은
|
||
-- `excelAppconfirmList` 등 혼합 케이스). → information_schema 기반 동적 DROP 으로 교체.
|
||
-- ③ 로컬 DB 에만 있고 저장소 어느 SQL 에도 없던 인덱스 2건
|
||
-- (COMTNEMPLYRINFO.IX_EMPLYR_ESNTL, BIZ_GROUP.IX_BIZ_GROUP_ESNTL) 을 PART 1 에 추가.
|
||
-- ④ PART 4 끝단 '반영 후 검증' SELECT 들도 뷰명을 소문자로 직접 참조하고 있었다.
|
||
-- 운영에서는 뷰 교체가 **성공한 직후** 이 SELECT 가 1146 으로 터져, 성공한 컷오버가
|
||
-- 실패처럼 보인다. → @v_master/@v_info 를 쓰는 동적 SQL 로 교체.
|
||
-- ⑤ PART 1 죽은 뷰 8개는 DROP 전에 정의를 privacy_view_def_backup 에 백업하도록 했다.
|
||
-- (backup/privacy.mber_nm_dead_views_backup_20260810.sql 은 로컬 소문자 캡처라
|
||
-- 운영 롤백 자산으로 쓸 수 없다 — 해당 파일에도 경고를 달아 두었다)
|
||
-- ⑥ **[치명] PART 4 가 스키마 한정자를 고려하지 않고 있었다.**
|
||
-- information_schema.views.VIEW_DEFINITION 은 테이블을 항상 `csv`.`TBL` 로 저장하는데
|
||
-- (mysqldump 출력에는 없다), 2026-08-11 통합 시 원본 ddl 파일에 있던 `csv`. 가
|
||
-- 누락돼 5개 치환 중 4개가 **어느 환경에서도** 불발되는 상태였다.
|
||
-- dev 가 멀쩡한 것은 단독 파일(ddl/privacy.mber_nm_view_hash_cutover_mysql.sql,
|
||
-- `csv`. 포함)로 컷오버했기 때문이고, 이 통합본으로는 한 번도 성공한 적이 없다.
|
||
-- → 치환 전에 `REPLACE(@def, CONCAT('`', DATABASE(), '`.'), '')` 로 한정자를 제거한다.
|
||
-- (CREATE OR REPLACE VIEW 시 MySQL 이 다시 붙이므로 결과물은 동일)
|
||
-- 검증: 운영 런타임 형식(대문자+한정자) 재현 후 MySQL 엔진에서 실행 →
|
||
-- hash_cnt=4 / uses_enc=1 / plaintext_gone=1.
|
||
-- #####################################################################
|
||
|
||
|
||
-- =====================================================================
|
||
-- PART 1. DDL (테이블 생성 / 컬럼 확장) ※ 시드 INSERT는 PART 2로 분리
|
||
-- =====================================================================
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.account_mng_policy_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 계정 관리 정책 : ACCOUNT_MNG_POLICY (단일행 정책 테이블)
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 장기 미사용 계정 관리)
|
||
-- 개발항목 #13(정책 설정) + #9(휴면 기준일수 정책값화) - 담당: arko-privacy-authctrl (Fork A)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [용도] 휴면 기준일수·사전알림 시점·삭제 보존기간·배치주기 등 계정 관리 정책을
|
||
-- 하드코딩이 아닌 관리자 설정값으로 운영하기 위한 단일행 정책 테이블.
|
||
-- [#9] 휴면 select(EgovLoginUsr_SQL_Mysql.xml)의 365 하드코딩을
|
||
-- (SELECT DORMANT_DAY FROM ACCOUNT_MNG_POLICY) 로 대체(미설정 시 365 fallback).
|
||
-- [#10] ALERT_D1/D2/D3(기본 30/7/0) = 휴면예정 D-30/D-7/D-Day 사전알림 시점.
|
||
-- ADMIN_EMAIL = 사전알림 관리자 수신 주소.
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS ACCOUNT_MNG_POLICY (
|
||
POLICY_ID INT NOT NULL DEFAULT 1 COMMENT '정책 식별자(단일행=1)',
|
||
DORMANT_DAY INT NOT NULL DEFAULT 365 COMMENT '휴면 전환 기준 미접속 일수(#9)',
|
||
ALERT_D1 INT NOT NULL DEFAULT 30 COMMENT '사전알림 1차(휴면 D-일수)',
|
||
ALERT_D2 INT NOT NULL DEFAULT 7 COMMENT '사전알림 2차(휴면 D-일수)',
|
||
ALERT_D3 INT NOT NULL DEFAULT 0 COMMENT '사전알림 3차(D-Day, 0=당일)',
|
||
DELETE_PRESERVE_DAY INT NOT NULL DEFAULT 365 COMMENT '휴면/탈퇴 후 삭제 보존기간(일)',
|
||
BATCH_CYCLE VARCHAR(20) NOT NULL DEFAULT 'DAILY' COMMENT '배치 주기(DAILY/WEEKLY/MONTHLY)',
|
||
ADMIN_EMAIL VARCHAR(200) NULL COMMENT '사전알림 관리자 수신 이메일',
|
||
UPD_ID VARCHAR(20) NULL COMMENT '최종 수정자',
|
||
UPD_DT DATETIME NULL COMMENT '최종 수정일시',
|
||
PRIMARY KEY (POLICY_ID)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='계정 관리 정책(개인정보보호 SFR-002, 단일행)';
|
||
|
||
-- 기본 정책 시드(없을 때만). 기준 365일·알림 D-30/7/0·보존 365일.
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.account_unlock_history_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 계정 잠금 해제 이력 : ACCOUNT_UNLOCK_HISTORY
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SER-001, 계정 잠금 정책 정비)
|
||
-- 개발항목 #24 - 담당: arko-privacy-authctrl (Fork A)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] 잠긴 계정 해제를 DB 직접조작이 아니라 관리자 화면(EgovAccountUnlockManage)
|
||
-- 에서 처리하고, 해제자·해제일시·대상·사유를 이력으로 남긴다.
|
||
-- [무결성] 애플리케이션 계층에 UPDATE/DELETE statement 미정의(INSERT/SELECT 한정).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS ACCOUNT_UNLOCK_HISTORY (
|
||
UNLOCK_HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '해제 이력 일련번호(PK)',
|
||
TRGET_ID VARCHAR(50) NULL COMMENT '해제 대상 계정 ID(전체해제 시 NULL 가능)',
|
||
TRGET_SE VARCHAR(10) NULL COMMENT '대상 회원구분 GNR/GRO/ENT/USR',
|
||
UNLOCK_TY CHAR(1) NOT NULL DEFAULT 'I' COMMENT '해제 유형 I:개별 A:전체',
|
||
UNLOCK_USER_ID VARCHAR(50) NULL COMMENT '해제 처리자(관리자) ID',
|
||
UNLOCK_RESN VARCHAR(500) NULL COMMENT '해제 사유',
|
||
UNLOCK_IP VARCHAR(45) NULL COMMENT '해제자 접속 IP(IPv6 대응)',
|
||
UNLOCK_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '해제 일시',
|
||
PRIMARY KEY (UNLOCK_HIST_ID),
|
||
KEY IX_AUH_TRGET (TRGET_ID, UNLOCK_DT),
|
||
KEY IX_AUH_USER (UNLOCK_USER_ID, UNLOCK_DT),
|
||
KEY IX_AUH_DT (UNLOCK_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='계정 잠금 해제 이력(개인정보보호 SER-001 #24)';
|
||
|
||
|
||
-- =====================================================================
|
||
-- 계정 잠금 임계값 10회 → 5회 (#24)
|
||
-- 임계값은 하드코딩이 아니라 LOGIN_LOCK_COUNT.LOCK_COUNT(단일행) 데이터에서 조회됨
|
||
-- (loginDAO.selectFailTotalCount). 아래 UPDATE 로 운영 정책값을 5회로 조정한다.
|
||
-- ※ 소스(EgovLoginController)에는 값 부재 시 기본 5회 fallback 이 반영되어 있음.
|
||
-- =====================================================================
|
||
UPDATE LOGIN_LOCK_COUNT SET LOCK_COUNT = 5;
|
||
-- 행이 없을 경우 대비(최초 세팅):
|
||
-- INSERT INTO LOGIN_LOCK_COUNT (LOCK_COUNT) SELECT 5 FROM DUAL
|
||
-- WHERE NOT EXISTS (SELECT 1 FROM LOGIN_LOCK_COUNT);
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.author_change_history_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 권한 변경 이력 테이블 : AUTHOR_CHANGE_HISTORY
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001, 역할별 차등 접근/권한 변경 추적)
|
||
-- 개발항목 #4 - 담당: arko-privacy-authctrl (Fork A → 오케스트레이터 완료)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] 권한 신설·변경·회수(#1 getAuthorities 대상 COMTNEMPLYRSCRTYESTBS 등)의
|
||
-- 모든 변경을 변경자·변경일시·변경전/후 권한과 함께 이력으로 적재한다.
|
||
-- 권한 신청·승인 워크플로(#3)의 승인 반영 시점에 권한반영과 단일 트랜잭션으로 적재.
|
||
--
|
||
-- [보관·파기] 3년(1,095일) 이상 보관 전제. 월 1회 @Scheduled 파기 배치(#5)가
|
||
-- 보관기간(1,095일) 경과 이력만 선별 파기한다. 개별 삭제 API 미제공.
|
||
-- 다운로드 시 사유 입력 필수 + 다운로드 이력 적재(#6)는 별도 처리.
|
||
--
|
||
-- [무결성] 이력 위·변조 억제를 위해 변경전/후 권한을 함께 보관(사후 대사 가능).
|
||
-- 애플리케이션 계층에 UPDATE statement 미정의(INSERT/SELECT/파기 DELETE 한정).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS AUTHOR_CHANGE_HISTORY (
|
||
HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호(PK)',
|
||
TRGET_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '변경 대상자 고유식별자(ESNTL_ID)',
|
||
TRGET_ID VARCHAR(20) NULL COMMENT '변경 대상자 계정ID(참고용)',
|
||
CHG_SE_CODE CHAR(1) NOT NULL COMMENT '변경구분 G:부여(grant) R:회수(revoke) M:변경(modify)',
|
||
BEFORE_AUTHOR VARCHAR(1000) NULL COMMENT '변경 전 권한(코드 CSV 또는 스냅샷)',
|
||
AFTER_AUTHOR VARCHAR(1000) NULL COMMENT '변경 후 권한(코드 CSV 또는 스냅샷)',
|
||
CHG_RESN VARCHAR(500) NULL COMMENT '변경 사유(신청/승인 워크플로 연계 #3)',
|
||
APROVL_ID VARCHAR(20) NULL COMMENT '승인자 계정ID(승인 워크플로 #3)',
|
||
CHANGER_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '변경자(처리자) 고유식별자',
|
||
CHANGER_ID VARCHAR(20) NULL COMMENT '변경자 계정ID(참고용)',
|
||
CHANGER_IP VARCHAR(45) NULL COMMENT '변경자 접속 IP(IPv6 대응)',
|
||
CHG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '변경 일시',
|
||
PRIMARY KEY (HIST_ID),
|
||
KEY IX_ACH_TRGET (TRGET_UNIQ_ID, CHG_DT),
|
||
KEY IX_ACH_CHANGER (CHANGER_UNIQ_ID, CHG_DT),
|
||
KEY IX_ACH_CHGDT (CHG_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='권한 변경 이력(개인정보보호 SFR-001, 3년 이상 보관)';
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.author_workflow_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 권한 신청·승인 워크플로 + 권한이력 다운로드 이력
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001, 권한 신설·변경·회수 승인통제)
|
||
-- 개발항목 #3(신청·승인 워크플로) / #6(권한이력 조회·다운로드) - 담당: arko-privacy-authctrl (Fork A 6차)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [#3] AUTHOR_CHANGE_REQUEST : 권한 변경 신청→승인/반려 워크플로.
|
||
-- 승인(A) 시 권한 반영(COMTNEMPLYRSCRTYESTBS)과 이력 적재(AUTHOR_CHANGE_HISTORY)를
|
||
-- 단일 트랜잭션으로 처리한다(egovframework.com..*Impl AOP tx advisor).
|
||
-- [#6] AUTHOR_HIST_DOWNLOAD_HISTORY : 권한이력 다운로드 증빙(사유 필수).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS AUTHOR_CHANGE_REQUEST (
|
||
REQUEST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '신청 일련번호(PK)',
|
||
TRGET_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '권한변경 대상자 고유식별자(ESNTL_ID)',
|
||
TRGET_ID VARCHAR(20) NULL COMMENT '권한변경 대상자 계정ID(참고용)',
|
||
REQUEST_SE CHAR(1) NOT NULL COMMENT '신청구분 N:신설(부여) C:변경 R:회수',
|
||
BEFORE_AUTHOR VARCHAR(1000) NULL COMMENT '신청 시점 현재 권한(스냅샷)',
|
||
AFTER_AUTHOR VARCHAR(1000) NULL COMMENT '신청 목표 권한(코드/CSV)',
|
||
REQUEST_RESN VARCHAR(500) NOT NULL COMMENT '신청 사유(필수)',
|
||
REQSTER_ID VARCHAR(20) NOT NULL COMMENT '신청자 계정ID',
|
||
REQSTER_UNIQ_ID VARCHAR(20) NULL COMMENT '신청자 고유식별자',
|
||
REQUEST_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '신청 일시',
|
||
CONFM_STTUS CHAR(1) NOT NULL DEFAULT 'R' COMMENT '처리상태 R:신청 A:승인 J:반려',
|
||
CONFM_ID VARCHAR(20) NULL COMMENT '승인/반려 처리자 계정ID',
|
||
CONFM_UNIQ_ID VARCHAR(20) NULL COMMENT '승인/반려 처리자 고유식별자',
|
||
CONFM_DT DATETIME NULL COMMENT '승인/반려 일시',
|
||
CONFM_RESN VARCHAR(500) NULL COMMENT '반려 사유',
|
||
PRIMARY KEY (REQUEST_ID),
|
||
KEY IX_ACR_TRGET (TRGET_UNIQ_ID, REQUEST_DT),
|
||
KEY IX_ACR_STTUS (CONFM_STTUS, REQUEST_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='권한 변경 신청·승인 워크플로(개인정보보호 SFR-001 #3)';
|
||
|
||
CREATE TABLE IF NOT EXISTS AUTHOR_HIST_DOWNLOAD_HISTORY (
|
||
DWLD_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '다운로드 일련번호(PK)',
|
||
DOWNLOADER_ID VARCHAR(20) NOT NULL COMMENT '다운로드자 계정ID',
|
||
DOWNLOADER_UNIQ_ID VARCHAR(20) NULL COMMENT '다운로드자 고유식별자',
|
||
DOWNLOADER_IP VARCHAR(45) NULL COMMENT '다운로드자 접속 IP(IPv6 대응)',
|
||
DWLD_RESN VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수·미입력 차단)',
|
||
SEARCH_COND VARCHAR(1000) NULL COMMENT '다운로드 시 검색조건 스냅샷',
|
||
DWLD_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 건수',
|
||
DWLD_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '다운로드 일시',
|
||
PRIMARY KEY (DWLD_ID),
|
||
KEY IX_AHD_DWLD (DOWNLOADER_ID, DWLD_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='권한이력 다운로드 증빙 이력(개인정보보호 SFR-001 #6)';
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [PDAL_JOB_MAPPING — 2026-08-11 제거]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 업무유형 매핑 테이블(PDAL_JOB_MAPPING) 생성 DDL 과 변경자 컬럼 확장 DDL 이
|
||
-- 여기 있었다(구 privacy.job_mapping_create_mysql.sql / job_mapping_audit_cols_mysql.sql).
|
||
--
|
||
-- [제거 사유] 이 테이블은 폐기된 서비스 AOP 수집 방식(PersonalDataLogAspect)의
|
||
-- 분류 기준이었다. 2026-08-07 수집이 MenuAccessLogInterceptor(메뉴명 기반)로 바뀌면서
|
||
-- 소비자가 없어졌고, 2026-08-11 관리 화면(메뉴 9100045)까지 삭제하면서 DDL 을 뺐다.
|
||
-- 접속기록의 수행업무(JOB_SE_CODE/JOB_SE_NM) 자체는 그대로 유지된다 — 값의 출처가
|
||
-- 매핑 테이블에서 COMTNMENUINFO 메뉴명으로 바뀐 것뿐이다(SFR-004 요건 회귀 아님).
|
||
--
|
||
-- ★ 기존 운영 DB 의 PDAL_JOB_MAPPING 테이블은 DROP 하지 않는다(별도 판단 사항).
|
||
-- FK 로 참조하는 테이블이 없고 조회하는 sqlMap 도 남아 있지 않아 방치해도 무해하다.
|
||
-- =====================================================================
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.download_history_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 개인정보 접속기록 다운로드 이력 : PDAL_DOWNLOAD_HISTORY
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
|
||
-- 개발항목 #42 - 담당: arko-privacy-auditlog (Fork B)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] 개인정보 접속기록(PERSONAL_DATA_ACCESS_LOG) 조회결과 다운로드 시,
|
||
-- 다운로드 사유(필수)·다운로드자·일시·IP·건수를 이력으로 남긴다.
|
||
-- 사유 미입력 시 다운로드는 서비스 계층에서 차단(본 테이블 미적재).
|
||
-- [원칙] 개인정보 본문 미적재 — 다운로드 검색조건 요약(SEARCH_COND)만 보관.
|
||
-- 이 테이블은 INSERT/SELECT 만 수행(위·변조 방지, UPDATE/DELETE 미정의).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS PDAL_DOWNLOAD_HISTORY (
|
||
DL_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '다운로드 이력 일련번호(PK)',
|
||
DOWNLOADER_ID VARCHAR(20) NOT NULL COMMENT '다운로드자 계정ID',
|
||
DOWNLOAD_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '다운로드 일시',
|
||
DOWNLOADER_IP VARCHAR(45) NULL COMMENT '다운로드자 접속 IP(IPv6 대응)',
|
||
DL_REASON VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수 — 미입력 시 차단)',
|
||
SEARCH_COND VARCHAR(1000) NULL COMMENT '다운로드 검색조건 요약(개인정보 본문 미포함)',
|
||
RESULT_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 건수',
|
||
REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록 일시',
|
||
PRIMARY KEY (DL_ID),
|
||
KEY IX_PDDH_DT (DOWNLOAD_DT),
|
||
KEY IX_PDDH_USER (DOWNLOADER_ID, DOWNLOAD_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='개인정보 접속기록 다운로드 이력(#42, SER-002)';
|
||
|
||
-- [Oracle] 시퀀스 필요:
|
||
-- CREATE SEQUENCE PDAL_DOWNLOAD_HISTORY_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.inspection_report_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 개인정보 접속기록 정기점검 리포트 : PDAL_INSPECTION_REPORT
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
|
||
-- 개발항목 #43 - 담당: arko-privacy-auditlog (Fork B)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] 월 1회 정기점검 배치가 전월분 접속기록을 집계해 1행으로 적재한다.
|
||
-- "월 1회 이상 점검 내역"을 화면 출력으로 제출(개인정보보호위 점검 대응).
|
||
--
|
||
-- [집계원] PERSONAL_DATA_ACCESS_LOG(#30) + 해시체인 무결성 검증(#40) 재사용.
|
||
-- [자리표시] 이상징후 5유형(#35)·다운로드 이력(#42)은 미구현 → 컬럼만 두고 배치가 0/요약만 적재.
|
||
-- [화면입력] INSPECTOR_ID/INSPECT_RESULT/ACTN_MATTER 는 배치가 아닌 점검자가 화면(#48)에서 입력.
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS PDAL_INSPECTION_REPORT (
|
||
RPT_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '리포트 일련번호(PK)',
|
||
RPT_YM CHAR(6) NOT NULL COMMENT '점검 대상 연월(전월, yyyyMM)',
|
||
PERIOD_FROM DATETIME NOT NULL COMMENT '집계 시작일시',
|
||
PERIOD_TO DATETIME NOT NULL COMMENT '집계 종료일시',
|
||
TOTAL_LOG_CNT INT NOT NULL DEFAULT 0 COMMENT '총 접속기록 건수',
|
||
FAIL_CNT INT NOT NULL DEFAULT 0 COMMENT '처리결과 F(실패/차단) 건수 = 이상징후 일부',
|
||
ABNORMAL_SUMMARY VARCHAR(2000) NULL COMMENT '이상징후 5유형 요약(#35 연계 전 자리표시)',
|
||
INTEGRITY_RESULT VARCHAR(20) NOT NULL DEFAULT 'OK' COMMENT '해시체인 무결성: OK / VIOLATION',
|
||
INTEGRITY_MISMATCH_ID BIGINT NULL COMMENT '최초 불일치 LOG_ID(위반 시)',
|
||
INTEGRITY_TOTAL_CNT INT NOT NULL DEFAULT 0 COMMENT '무결성 검증 대상 건수',
|
||
HANDLER_SUMMARY VARCHAR(4000) NULL COMMENT '취급자별 처리현황 요약',
|
||
DOWNLOAD_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 이력 건수(#42 연계 전 0)',
|
||
INSPECTOR_ID VARCHAR(20) NULL COMMENT '점검자 계정ID(화면 입력, #44)',
|
||
INSPECT_RESULT VARCHAR(500) NULL COMMENT '점검결과(화면 입력, #44)',
|
||
ACTN_MATTER VARCHAR(1000) NULL COMMENT '조치사항(화면 입력, #44)',
|
||
INSPECT_DT DATETIME NULL COMMENT '점검일시(점검결과 입력 시각, #44 감사증빙)',
|
||
REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '리포트 생성일시',
|
||
PRIMARY KEY (RPT_ID),
|
||
UNIQUE KEY UX_PIR_YM (RPT_YM),
|
||
KEY IX_PIR_REGDT (REG_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='개인정보 접속기록 정기점검 리포트(#43, 월 1회)';
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.pdal_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 개인정보 접속기록 표준 테이블 : PERSONAL_DATA_ACCESS_LOG
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001 / SER-002)
|
||
-- 개발항목 #30 - 담당: arko-privacy-auditlog (Fork B)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [설계 원칙] 신규 스키마 설계가 아니라 기존 표준 PERSONAL_DATA_DML_HISTORY 를
|
||
-- 표준 접속기록 테이블로 "승격" + 보관/무결성 컬럼 확장이다.
|
||
--
|
||
-- [기존 PERSONAL_DATA_DML_HISTORY → PERSONAL_DATA_ACCESS_LOG 컬럼 매핑]
|
||
-- CHG_USER_ID → CONECT_ID (① 사용자 식별자)
|
||
-- CREATE_DATE → CONECT_DT (② 접속일시, SYSDATE())
|
||
-- CHG_CONECT_IP → CONECT_IP (③ 접속지 IP) * VARCHAR(45) IPv6 대응
|
||
-- UI_GUBUN → JOB_SE_CODE/NM (④ 수행업무 유형) * 업무유형 매핑 테이블로 코드/명 분리
|
||
-- ESNTL_ID → TRGET_ID (⑤ 처리대상 식별자)
|
||
-- DML_GUBUN → DML_GUBUN (처리구분 S/I/U/D 그대로 계승)
|
||
-- CHG_USER_SE → USER_SE (처리자 구분)
|
||
-- [신규 확장 컬럼]
|
||
-- TRGET_CN - 처리대상 건수/범위 (⑤ 보강)
|
||
-- PROCESS_RESULT - 처리결과 S:성공 F:실패/차단 (비정상접근 탐지 5유형 적재 겸용)
|
||
-- HASH_VAL - 위·변조 방지 해시 (SHA-256, 개발항목 #39)
|
||
-- PREV_HASH_VAL - 직전 레코드 해시 (해시체인)
|
||
--
|
||
-- [보관·파기] 730일 보관 전제. 월 단위 RANGE COLUMNS 파티셔닝으로
|
||
-- 파기 배치가 만료 파티션을 ALTER TABLE ... DROP PARTITION 으로 제거
|
||
-- (개별 DELETE 미사용). 애플리케이션 계층에 UPDATE/DELETE statement 미정의.
|
||
-- [분리 원칙] COMTNSYSLOG 의 7일 삭제정책은 본 테이블에 절대 적용하지 않는다.
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS PERSONAL_DATA_ACCESS_LOG
|
||
(
|
||
LOG_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '로그 일련번호',
|
||
CONECT_ID VARCHAR(20) NOT NULL COMMENT '① 사용자 식별자(ID)',
|
||
CONECT_DT DATETIME NOT NULL COMMENT '② 접속일시(파티션 키)',
|
||
CONECT_IP VARCHAR(45) NOT NULL COMMENT '③ 접속지 정보(IP, IPv6 대응)',
|
||
JOB_SE_CODE VARCHAR(30) NOT NULL COMMENT '④ 수행업무 유형 코드',
|
||
JOB_SE_NM VARCHAR(100) NOT NULL COMMENT '④ 수행업무 유형 명(화면/기능명)',
|
||
TRGET_ID VARCHAR(50) NULL COMMENT '⑤ 처리대상 식별자',
|
||
TRGET_CN VARCHAR(200) NULL COMMENT '⑤ 처리대상 건수/범위',
|
||
DML_GUBUN CHAR(1) NOT NULL COMMENT '처리구분 S:조회 I:등록 U:수정 D:삭제',
|
||
PROCESS_RESULT CHAR(1) NOT NULL DEFAULT 'S' COMMENT '처리결과 S:성공 F:실패/차단',
|
||
USER_SE VARCHAR(10) NULL COMMENT '처리자 구분',
|
||
HASH_VAL VARCHAR(64) NOT NULL COMMENT '위·변조 방지 해시(SHA-256 Hex)',
|
||
PREV_HASH_VAL VARCHAR(64) NULL COMMENT '직전 레코드 해시(해시체인)',
|
||
PRIMARY KEY (LOG_ID, CONECT_DT),
|
||
KEY IDX_PDAL_01 (CONECT_ID, CONECT_DT), -- 접속자 ID 검색 (현행 CONECT_MTHD LIKE 결함 해소)
|
||
KEY IDX_PDAL_02 (CONECT_DT), -- 기간 검색 / 파티션 프루닝
|
||
KEY IDX_PDAL_03 (TRGET_ID, CONECT_DT), -- 처리대상 추적
|
||
KEY IDX_PDAL_04 (JOB_SE_CODE, CONECT_DT) -- 업무유형/이상탐지유형 집계
|
||
)
|
||
ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||
COMMENT='개인정보 접속기록 표준 테이블(PERSONAL_DATA_DML_HISTORY 승격, 730일 보관)'
|
||
PARTITION BY RANGE COLUMNS(CONECT_DT)
|
||
(
|
||
-- 파티션 키(CONECT_DT)를 PK에 포함해야 RANGE COLUMNS 파티셔닝 성립 (MySQL/MariaDB 제약)
|
||
-- 월 단위 사전 생성. 유지보수 배치가 매월 다음 달 파티션을 ADD PARTITION 으로 선행 생성하고,
|
||
-- 파기 배치(월 1회)가 보관기간(기본 730일) 경과 파티션을 DROP PARTITION 으로 제거한다.
|
||
-- ※ MAXVALUE 파티션은 두지 않는다 — 만료 데이터가 catch-all 에 갇혀 DROP 불가해지는 것을 방지.
|
||
PARTITION p202607 VALUES LESS THAN ('2026-08-01'),
|
||
PARTITION p202608 VALUES LESS THAN ('2026-09-01'),
|
||
PARTITION p202609 VALUES LESS THAN ('2026-10-01'),
|
||
PARTITION p202610 VALUES LESS THAN ('2026-11-01'),
|
||
PARTITION p202611 VALUES LESS THAN ('2026-12-01'),
|
||
PARTITION p202612 VALUES LESS THAN ('2027-01-01'),
|
||
PARTITION p202701 VALUES LESS THAN ('2027-02-01'),
|
||
PARTITION p202702 VALUES LESS THAN ('2027-03-01')
|
||
)
|
||
;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- [유지보수 참고] 월별 파티션 추가/삭제 예시 (배치가 동적으로 수행)
|
||
--
|
||
-- -- 다음 달 파티션 선행 생성
|
||
-- ALTER TABLE PERSONAL_DATA_ACCESS_LOG
|
||
-- ADD PARTITION (PARTITION p202703 VALUES LESS THAN ('2027-04-01'));
|
||
--
|
||
-- -- 보관기간(730일) 경과 파티션 파기
|
||
-- ALTER TABLE PERSONAL_DATA_ACCESS_LOG DROP PARTITION p202607;
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.pdal_purge_history_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 개인정보 접속기록 파기·증설 이력 : PDAL_PURGE_HISTORY
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001)
|
||
-- 개발항목 #37 - 담당: arko-privacy-auditlog (Fork B)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] PERSONAL_DATA_ACCESS_LOG 의 보관정책(730일) 파기 배치가 수행한
|
||
-- 파티션 DROP(파기)·ADD(선행 증설) 내역을 증빙으로 기록한다.
|
||
-- 개인정보 접속기록의 유일한 삭제 경로가 "파티션 DROP"임을 감사에서 입증하는 근거.
|
||
--
|
||
-- [원칙] 접속기록 본문(PERSONAL_DATA_ACCESS_LOG)에는 UPDATE/DELETE 를 정의하지 않으며,
|
||
-- 파기는 개별 DELETE 가 아니라 파티션 단위 DROP 으로만 수행한다(개발항목 #37).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS PDAL_PURGE_HISTORY
|
||
(
|
||
PURGE_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호',
|
||
PARTITION_NAME VARCHAR(64) NOT NULL COMMENT '대상 파티션명(예: p202607)',
|
||
BOUNDARY_DT VARCHAR(30) NULL COMMENT '파티션 상한 경계값(LESS THAN, 예: 2026-08-01)',
|
||
PURGE_TYPE VARCHAR(10) NOT NULL COMMENT '유형 DROP:파기 ADD:선행증설',
|
||
PURGE_DT DATETIME NOT NULL COMMENT '수행 일시',
|
||
NOTE VARCHAR(300) NULL COMMENT '비고(보관기간·사유)',
|
||
PRIMARY KEY (PURGE_ID),
|
||
KEY IDX_PDAL_PURGE_01 (PURGE_DT),
|
||
KEY IDX_PDAL_PURGE_02 (PARTITION_NAME)
|
||
)
|
||
ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||
COMMENT='개인정보 접속기록 파티션 파기·증설 이력(개발항목 #37, 파기 증빙)';
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.otp_device_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- OTP(2단계 인증) 비밀키 영구 저장 테이블
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-003 / IAR-001, 추가 인증수단 1차 OTP)
|
||
-- 개발항목 No.18 후속 - 담당: arko-privacy-authctrl
|
||
-- 작성일: 2026-07-22 DB: MySQL 5.7/8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [배경] OtpSecretStore 기본 구현이 InMemory(재기동 시 유실)라 이를 대체하는
|
||
-- DB 영속 저장소(DaoOtpSecretStore)가 사용하는 테이블.
|
||
-- [보안] SECRET_ENC 는 OtpSecretCipher(AES-GCM) 암호문(Base64)만 저장한다.
|
||
-- 평문 비밀키/OTP 코드는 저장하지 않는다(SER-003).
|
||
-- [키] UNIQ_ID = 개인정보취급자 고유식별자(ESNTL_ID), 취급자당 1건(PK).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS OTP_DEVICE (
|
||
UNIQ_ID VARCHAR(20) NOT NULL COMMENT '개인정보취급자 고유식별자(ESNTL_ID, PK)',
|
||
SECRET_ENC VARCHAR(255) NOT NULL COMMENT 'OTP 비밀키 AES-GCM 암호문(Base64) - 평문 금지',
|
||
REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록(발급) 일시',
|
||
LAST_VERIFY_DT DATETIME NULL COMMENT '최종 검증 성공 일시(참고용)',
|
||
USE_CNT INT NOT NULL DEFAULT 0 COMMENT '검증 성공 누적 횟수(참고용)',
|
||
DEVICE_LABEL VARCHAR(100) NULL COMMENT '기기/계정 라벨(참고용)',
|
||
USE_YN CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 Y:사용 N:해지',
|
||
PRIMARY KEY (UNIQ_ID)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='OTP 2단계 인증 비밀키 영구 저장(개인정보보호 SFR-003 No.18)';
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.diapause_emplyr_extend_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 휴면(장기 미사용) 대상 확장 : COMTNDIAPAUSEMBER 회원유형 구분 컬럼 추가
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 장기 미사용 계정 관리)
|
||
-- 개발항목 #7 - 담당: arko-privacy-authctrl (Fork A)
|
||
-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [배경] 기존 휴면 대상은 일반회원(COMTNGNRLMBER)뿐이며, 휴면 테이블
|
||
-- COMTNDIAPAUSEMBER 에는 회원유형 구분 컬럼이 없다. 개인정보취급자
|
||
-- (COMTNEMPLYRINFO)를 휴면 대상에 포함(#7)하면서 일반회원 휴면/해제
|
||
-- 흐름과 혼선되지 않도록 회원유형 구분 컬럼을 신설한다.
|
||
--
|
||
-- [안전] DEFAULT 'GNR'(일반회원)로 신설 → 기존 적재 행은 자동 'GNR' 처리되어
|
||
-- 기존 selectDiapauseList(일반회원 join COMTNGNRLMBER) 흐름 무영향.
|
||
-- 취급자 휴면 행은 'EMP'로 적재하여 별도 select/notify 경로로 처리한다.
|
||
--
|
||
-- [멱등] MySQL 8 은 ADD COLUMN/INDEX IF NOT EXISTS 미지원 → information_schema 로
|
||
-- 존재를 확인해 없을 때만 실행한다(재실행 안전, MySQL 8 / MariaDB 공통).
|
||
-- =====================================================================
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNDIAPAUSEMBER' AND COLUMN_NAME = 'MBER_TY_CODE') > 0,
|
||
'SELECT ''SKIP: COMTNDIAPAUSEMBER.MBER_TY_CODE already exists'' AS msg',
|
||
'ALTER TABLE COMTNDIAPAUSEMBER
|
||
ADD COLUMN MBER_TY_CODE CHAR(3) NOT NULL DEFAULT ''GNR''
|
||
COMMENT ''회원유형 GNR:일반회원 EMP:개인정보취급자'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
-- 조회 성능(취급자/일반 분리 조회) 보조 인덱스
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNDIAPAUSEMBER' AND INDEX_NAME = 'IX_DIAPAUSE_MBERTY') > 0,
|
||
'SELECT ''SKIP: IX_DIAPAUSE_MBERTY already exists'' AS msg',
|
||
'ALTER TABLE COMTNDIAPAUSEMBER
|
||
ADD INDEX IX_DIAPAUSE_MBERTY (MBER_TY_CODE, DIAPAUSE_STAT)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.account_mng_policy_purge_useyn_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 계정 관리 정책 확장 : ACCOUNT_MNG_POLICY 에 파기 배치 사용여부 컬럼 추가
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 개발항목 #12)
|
||
-- 작성일: 2026-07-28
|
||
-- ---------------------------------------------------------------------
|
||
-- 파기(#12)는 되돌릴 수 없는 처리라 배치를 항상 등록해 두되 실제 수행 여부를
|
||
-- 이 플래그로 제어한다('N'이면 서비스가 즉시 0건 반환). 기본 'N'이므로 설치
|
||
-- 직후에는 아무것도 파기되지 않으며, 관리자가 계정 관리 정책 화면(#13)에서
|
||
-- '사용'으로 바꿔야 시작된다. 파기 기준일은 기존 DELETE_PRESERVE_DAY 를 사용.
|
||
--
|
||
-- ※ 신규 설치(위 PART 1 에서 ACCOUNT_MNG_POLICY 를 막 생성한 경우)에는 컬럼이
|
||
-- 없으므로 추가되고, 기존 환경 재실행 시에는 존재 확인 후 건너뛴다(재실행 안전).
|
||
-- =====================================================================
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'ACCOUNT_MNG_POLICY' AND COLUMN_NAME = 'PURGE_USE_YN') > 0,
|
||
'SELECT ''SKIP: ACCOUNT_MNG_POLICY.PURGE_USE_YN already exists'' AS msg',
|
||
'ALTER TABLE ACCOUNT_MNG_POLICY
|
||
ADD COLUMN PURGE_USE_YN CHAR(1) NOT NULL DEFAULT ''N''
|
||
COMMENT ''장기 미사용 계정 파기 배치 사용여부 Y:사용 N:미사용(#12)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.account_delete_history_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 장기 미사용 계정 개인정보 파기 이력 : ACCOUNT_DELETE_HISTORY
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 개발항목 #12 계정 삭제 기능)
|
||
-- 작성일: 2026-07-28 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] 휴면 확정(COMTNDIAPAUSEMBER.DIAPAUSE_STAT='P') 후 정책상 보존기간
|
||
-- (ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY)이 경과한 계정의 개인정보를
|
||
-- 파기(마스킹)한 이력을 남긴다. 실제 파기 방식은 행 DELETE가 아니라
|
||
-- PII 컬럼 마스킹(기존 회원탈퇴 MBER_STTUS='D' 컨벤션과 동일) — 다른
|
||
-- 테이블(게시글·마일리지·활동확인 등)이 MBER_ID/EMPLYR_ID를 참조하는
|
||
-- 소프트 조인이 많아 행 자체를 지우면 그쪽이 깨지기 때문.
|
||
--
|
||
-- [보존기간] ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY 값을 실행 시점에 그대로
|
||
-- 조회해 사용(하드코딩 없음). 이 테이블의 PRESERVE_DAY 컬럼은
|
||
-- 그 시점에 적용된 값의 스냅샷(정책이 나중에 바뀌어도 이력은 불변).
|
||
--
|
||
-- [처리량 제한] 배치가 오래 정지돼 있다가 재가동되는 날 백로그가 한꺼번에
|
||
-- 몰리는 것을 막기 위해 1회 처리건수를 상한(LIMIT 200)으로 제한.
|
||
--
|
||
-- ★ 배치(BatchScheduler.purgeExpiredAccountScheduler)는 파기가 되돌릴 수 없는
|
||
-- 처리라 기본 비활성(@Scheduled 주석) 상태다. 보존기간 확정·운영협의 후 활성화.
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS ACCOUNT_DELETE_HISTORY (
|
||
DEL_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '파기 일련번호(PK)',
|
||
MBER_ID VARCHAR(32) NOT NULL COMMENT '파기 대상 계정ID(MBER_ID/EMPLYR_ID)',
|
||
MBER_TY_CODE CHAR(3) NOT NULL COMMENT '회원유형 GNR:일반회원 EMP:개인정보취급자',
|
||
ESNTL_ID VARCHAR(20) NULL COMMENT '고유식별자(파기 시점 스냅샷, 참고용)',
|
||
DIAPAUSE_DT DATETIME NULL COMMENT '휴면 확정 기준일시(파기 판단 기준)',
|
||
PRESERVE_DAY INT NOT NULL COMMENT '파기 시점 적용된 보존기간(정책값 스냅샷, 일)',
|
||
DEL_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '파기 처리 일시',
|
||
PRIMARY KEY (DEL_ID),
|
||
KEY IX_ADH_MBER (MBER_ID),
|
||
KEY IX_ADH_DELDT (DEL_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='장기 미사용 계정 개인정보 파기 이력(개인정보보호 SFR-002 #12)';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.login_history_extend_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 인증 실패 이력 확장 : LOGIN_HISTORY (IP 기록 · 보관기간)
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SER-001, 개발항목 #22-1)
|
||
-- 작성일: 2026-08-05
|
||
-- ---------------------------------------------------------------------
|
||
-- [목적] #22 로 살아난 실패이력 적재에 ① 접속 IP ② 보존(성공 시 DELETE→소프트
|
||
-- 리셋) ③ 보관기간 정책을 추가한다. 잠금 카운트는 RESET_YN='N' 인
|
||
-- 당일 행만 세므로 계정잠금 동작은 종전과 동일하다.
|
||
--
|
||
-- ★ 이 ALTER 는 sqlmap 동반 배포 필수 ★
|
||
-- loginDAO.insertFailHistory 가 컬럼목록 없는 `VALUES(...)` 3값 INSERT 라
|
||
-- 컬럼 추가 후 ERROR 1136(Column count doesn't match)로 실패한다.
|
||
-- 컬럼 명시 INSERT 로 교체된 EgovLoginUsr_SQL_Mysql.xml 과 함께 배포할 것.
|
||
--
|
||
-- [재실행] MySQL 8 은 ADD COLUMN/KEY IF NOT EXISTS 미지원 → 항목마다 information_schema
|
||
-- 로 존재를 확인해 없을 때만 실행한다(일부만 적용된 환경도 나머지가 채워짐).
|
||
-- =====================================================================
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'LOGIN_HISTORY' AND COLUMN_NAME = 'CONECT_IP') > 0,
|
||
'SELECT ''SKIP: LOGIN_HISTORY.CONECT_IP already exists'' AS msg',
|
||
'ALTER TABLE LOGIN_HISTORY
|
||
ADD COLUMN CONECT_IP VARCHAR(45) NULL COMMENT ''접속 IP(#22-1, ClientIpResolver 취득)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'LOGIN_HISTORY' AND COLUMN_NAME = 'RESET_YN') > 0,
|
||
'SELECT ''SKIP: LOGIN_HISTORY.RESET_YN already exists'' AS msg',
|
||
'ALTER TABLE LOGIN_HISTORY
|
||
ADD COLUMN RESET_YN CHAR(1) NOT NULL DEFAULT ''N''
|
||
COMMENT ''잠금카운터 리셋여부 Y:성공으로 리셋됨 N:유효(#22-1)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'LOGIN_HISTORY' AND INDEX_NAME = 'IX_LOGIN_HISTORY_01') > 0,
|
||
'SELECT ''SKIP: IX_LOGIN_HISTORY_01 already exists'' AS msg',
|
||
'ALTER TABLE LOGIN_HISTORY ADD KEY IX_LOGIN_HISTORY_01 (CREATE_DATE)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'LOGIN_HISTORY' AND INDEX_NAME = 'IX_LOGIN_HISTORY_02') > 0,
|
||
'SELECT ''SKIP: IX_LOGIN_HISTORY_02 already exists'' AS msg',
|
||
'ALTER TABLE LOGIN_HISTORY ADD KEY IX_LOGIN_HISTORY_02 (ID, GUBUN, CREATE_DATE)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'ACCOUNT_MNG_POLICY' AND COLUMN_NAME = 'LOGIN_HIST_KEEP_DAY') > 0,
|
||
'SELECT ''SKIP: ACCOUNT_MNG_POLICY.LOGIN_HIST_KEEP_DAY already exists'' AS msg',
|
||
'ALTER TABLE ACCOUNT_MNG_POLICY
|
||
ADD COLUMN LOGIN_HIST_KEEP_DAY INT NOT NULL DEFAULT 730
|
||
COMMENT ''인증 실패 이력 보관기간(일, 기본 730=2년, #22-1)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.conect_log_extend_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 로그인·로그아웃 접속기록 확장 : COMTNLOGINLOG + CONECT_IP_LOCATION
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SER-001 / SER-002)
|
||
-- 개발항목 62 - 관리자 로그인·로그아웃 접속기록 조회·다운로드
|
||
-- 작성일: 2026-08-07 DB: MySQL 8 / MariaDB
|
||
-- ---------------------------------------------------------------------
|
||
-- [AS-IS 실측 2026-08-07] COMTNLOGINLOG 는 이미 살아 있으나(373,959행)
|
||
-- CONECT_IP 가 전 건 NULL 이고 접속위치 컬럼이 없다. 인덱스도 PK 하나뿐.
|
||
-- 또 CONECT_ID(고유식별자) 적재값이 16자리 hex 라 회원 테이블 4종 어디와도 0건 매칭된다(실측).
|
||
-- → 접속 계정ID·사용자구분을 별도 컬럼(CONECT_ACNT_ID / CONECT_USER_SE)에 직접 적재한다.
|
||
-- ★★ sqlmap(EgovLoginLog_SQL_Mysql.xml) 동반 배포 필수 ★★
|
||
-- logInsertLoginLog 가 CONECT_LC / CONECT_ACNT_ID / CONECT_USER_SE 를 컬럼 목록에 넣도록 바뀐다.
|
||
-- DDL 없이 sqlmap 만 올리면 Unknown column 으로 접속기록 적재가 깨진다.
|
||
-- =====================================================================
|
||
|
||
SET @ddl := IF(
|
||
(SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND COLUMN_NAME = 'CONECT_IP') >= 45,
|
||
'SELECT ''SKIP: COMTNLOGINLOG.CONECT_IP already >= 45'' AS msg',
|
||
'ALTER TABLE COMTNLOGINLOG
|
||
MODIFY COLUMN CONECT_IP VARCHAR(45) NULL COMMENT ''접속 IP(IPv6 대응)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND COLUMN_NAME = 'CONECT_LC') > 0,
|
||
'SELECT ''SKIP: COMTNLOGINLOG.CONECT_LC already exists'' AS msg',
|
||
'ALTER TABLE COMTNLOGINLOG
|
||
ADD COLUMN CONECT_LC VARCHAR(100) NULL COMMENT ''접속위치명(적재시점 스냅샷)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND COLUMN_NAME = 'CONECT_ACNT_ID') > 0,
|
||
'SELECT ''SKIP: COMTNLOGINLOG.CONECT_ACNT_ID already exists'' AS msg',
|
||
'ALTER TABLE COMTNLOGINLOG
|
||
ADD COLUMN CONECT_ACNT_ID VARCHAR(20) NULL COMMENT ''접속 계정ID(로그인 ID)'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND COLUMN_NAME = 'CONECT_USER_SE') > 0,
|
||
'SELECT ''SKIP: COMTNLOGINLOG.CONECT_USER_SE already exists'' AS msg',
|
||
'ALTER TABLE COMTNLOGINLOG
|
||
ADD COLUMN CONECT_USER_SE VARCHAR(3) NULL COMMENT ''사용자구분 USR/GNR/GRO/ENT'''
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND INDEX_NAME = 'IX_CLG_ACNT_DT') > 0,
|
||
'SELECT ''SKIP: IX_CLG_ACNT_DT already exists'' AS msg',
|
||
'CREATE INDEX IX_CLG_ACNT_DT ON COMTNLOGINLOG (CONECT_ACNT_ID, CREAT_DT)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND INDEX_NAME = 'IX_CLG_DT') > 0,
|
||
'SELECT ''SKIP: IX_CLG_DT already exists'' AS msg',
|
||
'CREATE INDEX IX_CLG_DT ON COMTNLOGINLOG (CREAT_DT)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND INDEX_NAME = 'IX_CLG_ID_DT') > 0,
|
||
'SELECT ''SKIP: IX_CLG_ID_DT already exists'' AS msg',
|
||
'CREATE INDEX IX_CLG_ID_DT ON COMTNLOGINLOG (CONECT_ID, CREAT_DT)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND INDEX_NAME = 'IX_CLG_MTHD_DT') > 0,
|
||
'SELECT ''SKIP: IX_CLG_MTHD_DT already exists'' AS msg',
|
||
'CREATE INDEX IX_CLG_MTHD_DT ON COMTNLOGINLOG (CONECT_MTHD, CREAT_DT)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @ddl := IF(
|
||
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNLOGINLOG' AND INDEX_NAME = 'IX_CLG_LC') > 0,
|
||
'SELECT ''SKIP: IX_CLG_LC already exists'' AS msg',
|
||
'CREATE INDEX IX_CLG_LC ON COMTNLOGINLOG (CONECT_LC)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
CREATE TABLE IF NOT EXISTS CONECT_IP_LOCATION (
|
||
LOC_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '일련번호(PK)',
|
||
IP_CIDR VARCHAR(45) NOT NULL COMMENT 'IP 대역(CIDR 표기)',
|
||
LOC_NM VARCHAR(100) NOT NULL COMMENT '접속위치명',
|
||
LOC_SE CHAR(1) NOT NULL DEFAULT 'E' COMMENT '내외부 구분 I:내부망 E:외부',
|
||
SORT_ORDR INT NOT NULL DEFAULT 0 COMMENT '정렬순서',
|
||
USE_AT CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 Y/N',
|
||
REMARK VARCHAR(200) NULL COMMENT '비고',
|
||
REGISTER_ID VARCHAR(20) NULL COMMENT '등록자 계정ID',
|
||
REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
|
||
UPDUSR_ID VARCHAR(20) NULL COMMENT '최종 변경자 계정ID',
|
||
UPDT_DT DATETIME NULL COMMENT '최종 변경일시',
|
||
PRIMARY KEY (LOC_ID),
|
||
UNIQUE KEY UK_CIL_CIDR (IP_CIDR)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='접속위치 매핑(IP 대역 → 위치명) — 로그인·로그아웃 접속기록 62';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.author_hist_download_job_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 권한변경 이력 CSV 비동기 다운로드 작업 : AUTHOR_HIST_DOWNLOAD_JOB
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001 / 개발항목 6)
|
||
-- ---------------------------------------------------------------------
|
||
-- 종전 동기 다운로드는 조건 전체를 메모리에 올려 응답에 실었다. 이 테이블은
|
||
-- '다운로드 요청'을 작업으로 등록해 두고 백그라운드가 파일을 만든 뒤, 완료된 것만
|
||
-- 내려받게 하기 위한 작업 대장이다. 상태 전이: W(대기) -> R(생성중) -> S(완료) -> X(파기), 실패는 F.
|
||
-- FILE_PATH 는 반드시 웹 루트 밖(Globals.privacyDownloadPath). 파일은 암호화 저장하고
|
||
-- EXPIRE_DT 경과분은 파기 배치가 삭제한다. 내려받기 증빙은 AUTHOR_HIST_DOWNLOAD_HISTORY 가 담당.
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS AUTHOR_HIST_DOWNLOAD_JOB (
|
||
JOB_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '작업 일련번호(PK)',
|
||
REQUESTER_ID VARCHAR(20) NOT NULL COMMENT '요청자 계정ID(취급자)',
|
||
REQUESTER_IP VARCHAR(45) NULL COMMENT '요청 IP(IPv6 대응 45)',
|
||
DL_REASON VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수)',
|
||
SEARCH_COND VARCHAR(1000) NULL COMMENT '검색조건 요약(표시용)',
|
||
F_TRGET_ID VARCHAR(50) NULL COMMENT '조건: 대상자ID(계정ID 또는 고유식별자)',
|
||
F_CHANGER_ID VARCHAR(50) NULL COMMENT '조건: 변경자 계정ID',
|
||
F_CHG_SE_CODE CHAR(1) NULL COMMENT '조건: 변경구분 G/M/R',
|
||
F_FROM_DT VARCHAR(19) NULL COMMENT '조건: 기간 시작',
|
||
F_TO_DT VARCHAR(19) NULL COMMENT '조건: 기간 종료',
|
||
STATUS CHAR(1) NOT NULL DEFAULT 'W' COMMENT '상태 W:대기 R:생성중 S:완료 F:실패 X:파기',
|
||
TOTAL_CNT INT NOT NULL DEFAULT 0 COMMENT '대상 총건수',
|
||
DONE_CNT INT NOT NULL DEFAULT 0 COMMENT '기록 완료 건수(진행률)',
|
||
FILE_NM VARCHAR(200) NULL COMMENT '내려받을 때 보여줄 파일명',
|
||
FILE_PATH VARCHAR(300) NULL COMMENT '서버 저장 절대경로(웹 루트 밖, 암호화 저장)',
|
||
FILE_SIZE BIGINT NOT NULL DEFAULT 0 COMMENT '암호화 파일 크기(byte)',
|
||
ERR_MSG VARCHAR(500) NULL COMMENT '실패 사유',
|
||
DOWNLOAD_CNT INT NOT NULL DEFAULT 0 COMMENT '내려받은 횟수',
|
||
LAST_DL_DT DATETIME NULL COMMENT '마지막 내려받은 일시',
|
||
REG_DT DATETIME NOT NULL COMMENT '요청 일시',
|
||
START_DT DATETIME NULL COMMENT '생성 시작 일시',
|
||
END_DT DATETIME NULL COMMENT '생성 종료 일시',
|
||
EXPIRE_DT DATETIME NULL COMMENT '파기 예정 일시(경과 시 파일 삭제)',
|
||
PRIMARY KEY (JOB_ID),
|
||
KEY IX_AHDJ_REQUESTER (REQUESTER_ID, REG_DT),
|
||
KEY IX_AHDJ_STATUS (STATUS, REG_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='권한변경 이력 CSV 비동기 다운로드 작업';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.account_action_history_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 장기 미사용 계정 조치 이력 : ACCOUNT_ACTION_HISTORY
|
||
-- 개발항목 #11 — 미사용계정 현황 화면의 '선택 잠금 / 선택 말소' 조치 기록.
|
||
-- 잠금 '해제' 이력은 ACCOUNT_UNLOCK_HISTORY(SER-001 #24)가 담당하며 역할이 다르다.
|
||
-- 애플리케이션 계층은 INSERT 만 정의(UPDATE/DELETE statement 없음).
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS ACCOUNT_ACTION_HISTORY (
|
||
ACTION_HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '조치 이력 일련번호(PK)',
|
||
TRGET_ID VARCHAR(50) NOT NULL COMMENT '조치 대상 계정 ID(EMPLYR_ID)',
|
||
TRGET_SE VARCHAR(10) NULL DEFAULT 'EMP' COMMENT '대상 회원구분(취급자 EMP)',
|
||
ACTION_TY CHAR(1) NOT NULL COMMENT '조치 유형 L:잠금(휴면확정) D:말소',
|
||
ACTION_USER_ID VARCHAR(50) NULL COMMENT '조치 처리자(관리자) ID',
|
||
ACTION_RESN VARCHAR(500) NULL COMMENT '조치 사유(화면 입력 필수)',
|
||
ACTION_IP VARCHAR(45) NULL COMMENT '조치자 접속 IP(IPv6 대응, X-Forwarded-For 보정값)',
|
||
NO_CONECT_DAY INT NULL DEFAULT 0 COMMENT '조치 시점의 미접속 일수(사후 검증용 스냅샷)',
|
||
ACTION_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '조치 일시',
|
||
PRIMARY KEY (ACTION_HIST_ID),
|
||
KEY IX_AAH_TRGET (TRGET_ID, ACTION_DT),
|
||
KEY IX_AAH_USER (ACTION_USER_ID, ACTION_DT),
|
||
KEY IX_AAH_DT (ACTION_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='장기 미사용 계정 조치(잠금/말소) 이력(개인정보보호 SFR-002 #11)';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.login_lock_exempt_create_mysql.sql]
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 로그인 실패 계정잠금 예외 : LOGIN_LOCK_EXEMPT (화이트리스트, SER-001 24)
|
||
-- ---------------------------------------------------------------------
|
||
-- 인증 실패 5회 잠금은 userSe 구분 없이 전 계정에 적용되고, 잠금 해제 화면도
|
||
-- 관리자 로그인이 있어야 들어갈 수 있어 최고관리자가 잠기면 아무도 해제할 수 없다.
|
||
-- 이 테이블은 그 break-glass 통로다. 잠금 '판정'만 면제하며, 실패 이력(LOGIN_HISTORY)
|
||
-- 적재와 2차 인증(OTP)은 예외 계정에도 그대로 적용된다.
|
||
-- 시드 없음 — 예외 대상 ID는 관리자 화면(접근통제 정책)에서 운영팀이 직접 등록한다.
|
||
-- =====================================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS LOGIN_LOCK_EXEMPT (
|
||
EXEMPT_ID VARCHAR(20) NOT NULL COMMENT '잠금 예외 대상 계정ID(COMTNEMPLYRINFO.EMPLYR_ID)',
|
||
EXEMPT_RESN VARCHAR(500) NOT NULL COMMENT '예외 등록 사유(필수 - 감사 대응)',
|
||
REG_ID VARCHAR(20) NULL COMMENT '등록한 관리자 계정ID',
|
||
REG_DT DATETIME NULL COMMENT '등록일시',
|
||
PRIMARY KEY (EXEMPT_ID)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='로그인 실패 계정잠금 예외 계정(개인정보보호 SER-001 24, 최고관리자 잠김 방지)';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.access_control_policy_create_mysql.sql] (2026-08-11 통합)
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 접근통제 정책 : ACCESS_CONTROL_POLICY (단일행 정책 테이블)
|
||
-- 문화품앗이 개인정보보호 기능개선 사업 (SER-001 요건 4·5 / 접근통제 정책 적용)
|
||
-- [용도] 계정 잠금 임계값·자동 로그아웃 시간·세션 재발급 여부를 관리자 설정값으로 운영.
|
||
-- [반영 경로] LOCK_THRESHOLD→LOGIN_LOCK_COUNT 동기화 / AUTO_LOGOUT_MIN→SessionPolicyInterceptor
|
||
-- / SESSION_REGEN_YN→SecondFactorController 세션 재발급
|
||
-- [재실행 안전] CREATE TABLE IF NOT EXISTS + 시드 NOT EXISTS 가드
|
||
-- =====================================================================
|
||
CREATE TABLE IF NOT EXISTS ACCESS_CONTROL_POLICY (
|
||
POLICY_ID INT NOT NULL DEFAULT 1 COMMENT '정책 식별자(단일행=1)',
|
||
LOCK_THRESHOLD INT NOT NULL DEFAULT 5 COMMENT '인증 실패 계정 잠금 임계값(회). 현행 10 -> 5',
|
||
UNLOCK_MODE VARCHAR(20) NOT NULL DEFAULT 'ADMIN_HIST' COMMENT '잠금 해제 방식 ADMIN_HIST:관리자화면+이력 / ADMIN_HIST_RESN:사유 필수',
|
||
AUTO_LOGOUT_MIN INT NOT NULL DEFAULT 30 COMMENT '미사용 자동 로그아웃(분). 현행 web.xml 60 -> 30',
|
||
EXPIRE_NOTICE_YN CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '세션 만료 임박 안내 사용여부 Y/N',
|
||
EXPIRE_NOTICE_MIN INT NOT NULL DEFAULT 5 COMMENT '만료 임박 안내 시점(만료 N분 전)',
|
||
SESSION_REGEN_YN CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '2차 인증 성공 시 세션 재발급(세션고정 차단) 사용여부 Y/N',
|
||
UPD_ID VARCHAR(20) NULL COMMENT '최종 수정자',
|
||
UPD_DT DATETIME NULL COMMENT '최종 수정일시',
|
||
PRIMARY KEY (POLICY_ID)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='접근통제 정책(개인정보보호 SER-001 요건4-5, 단일행)';
|
||
|
||
INSERT INTO ACCESS_CONTROL_POLICY
|
||
(POLICY_ID, LOCK_THRESHOLD, UNLOCK_MODE, AUTO_LOGOUT_MIN,
|
||
EXPIRE_NOTICE_YN, EXPIRE_NOTICE_MIN, SESSION_REGEN_YN, UPD_ID, UPD_DT)
|
||
SELECT 1, 5, 'ADMIN_HIST', 30, 'Y', 5, 'Y', 'SYSTEM', NOW()
|
||
FROM DUAL
|
||
WHERE NOT EXISTS (SELECT 1 FROM ACCESS_CONTROL_POLICY WHERE POLICY_ID = 1);
|
||
|
||
-- 인증 경로(EgovLoginController/EgovSpringSecurityLoginFilter)가 읽는 LOGIN_LOCK_COUNT 를
|
||
-- 정책값과 일치시킨다(화면 저장 시에도 AccessControlPolicyServiceImpl 이 동일하게 동기화).
|
||
UPDATE LOGIN_LOCK_COUNT
|
||
SET LOCK_COUNT = (SELECT LOCK_THRESHOLD FROM ACCESS_CONTROL_POLICY WHERE POLICY_ID = 1);
|
||
|
||
INSERT INTO LOGIN_LOCK_COUNT (LOCK_COUNT)
|
||
SELECT (SELECT LOCK_THRESHOLD FROM ACCESS_CONTROL_POLICY WHERE POLICY_ID = 1)
|
||
FROM DUAL
|
||
WHERE NOT EXISTS (SELECT 1 FROM LOGIN_LOCK_COUNT);
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.download_job_create_mysql.sql] (2026-08-11 통합)
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 개인정보 접속기록 CSV 비동기 다운로드 작업 : PDAL_DOWNLOAD_JOB (SFR-004-6 / SER-002)
|
||
-- [보안 전제] FILE_PATH 는 반드시 웹 루트 밖(Globals.privacyDownloadPath), 저장 파일은
|
||
-- 암호화(평문 CSV 미보관). EXPIRE_DT 경과분은 파기 배치가 파일 삭제+STATUS='X'.
|
||
-- [상태 전이] W(대기) -> R(생성중) -> S(완료) -> X(파기) / 실패는 F
|
||
-- =====================================================================
|
||
CREATE TABLE IF NOT EXISTS PDAL_DOWNLOAD_JOB (
|
||
JOB_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '작업 일련번호(PK)',
|
||
REQUESTER_ID VARCHAR(20) NOT NULL COMMENT '요청자 계정ID(취급자)',
|
||
REQUESTER_IP VARCHAR(45) NULL COMMENT '요청 IP(IPv6 대응 45)',
|
||
DL_REASON VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수)',
|
||
SEARCH_COND VARCHAR(1000) NULL COMMENT '검색조건 요약(개인정보 본문 미포함)',
|
||
F_CONECT_ID VARCHAR(50) NULL COMMENT '조건: 접속자ID',
|
||
F_JOB_SE_CODE VARCHAR(30) NULL COMMENT '조건: 수행업무 코드',
|
||
F_TRGET_ID VARCHAR(50) NULL COMMENT '조건: 처리대상 식별자',
|
||
F_CONECT_IP VARCHAR(45) NULL COMMENT '조건: 접속IP',
|
||
F_DML_GUBUN CHAR(1) NULL COMMENT '조건: 처리구분 S/I/U/D',
|
||
F_PROCESS_RESULT CHAR(1) NULL COMMENT '조건: 처리결과 S/F',
|
||
F_FROM_DT VARCHAR(19) NULL COMMENT '조건: 기간 시작',
|
||
F_TO_DT VARCHAR(19) NULL COMMENT '조건: 기간 종료',
|
||
STATUS CHAR(1) NOT NULL DEFAULT 'W' COMMENT '상태 W:대기 R:생성중 S:완료 F:실패 X:파기',
|
||
TOTAL_CNT INT NOT NULL DEFAULT 0 COMMENT '대상 총건수',
|
||
DONE_CNT INT NOT NULL DEFAULT 0 COMMENT '기록 완료 건수(진행률)',
|
||
FILE_NM VARCHAR(200) NULL COMMENT '내려받을 때 보여줄 파일명',
|
||
FILE_PATH VARCHAR(300) NULL COMMENT '서버 저장 절대경로(웹 루트 밖, 암호화 저장)',
|
||
FILE_SIZE BIGINT NOT NULL DEFAULT 0 COMMENT '암호화 파일 크기(byte)',
|
||
ERR_MSG VARCHAR(500) NULL COMMENT '실패 사유',
|
||
DOWNLOAD_CNT INT NOT NULL DEFAULT 0 COMMENT '내려받은 횟수',
|
||
LAST_DL_DT DATETIME NULL COMMENT '마지막 내려받은 일시',
|
||
REG_DT DATETIME NOT NULL COMMENT '요청 일시',
|
||
START_DT DATETIME NULL COMMENT '생성 시작 일시',
|
||
END_DT DATETIME NULL COMMENT '생성 종료 일시',
|
||
EXPIRE_DT DATETIME NULL COMMENT '파기 예정 일시(경과 시 파일 삭제)',
|
||
PRIMARY KEY (JOB_ID),
|
||
KEY IX_PDJ_REQUESTER (REQUESTER_ID, REG_DT),
|
||
KEY IX_PDJ_STATUS (STATUS, REG_DT)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='개인정보 접속기록 CSV 비동기 다운로드 작업';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.emplyr_scrty_index_mysql.sql] (2026-08-11 통합)
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 권한 매핑 조회 인덱스 : COMTNEMPLYRSCRTYESTBS(SCRTY_DTRMN_TRGET_ID) (SFR-001/SFR-002)
|
||
-- [문제] 인덱스 0개(운영 데이터 기준 약 8만행) — 로그인마다 권한조회 풀스캔, 계정당 약 200ms.
|
||
-- [재실행 안전] information_schema 로 존재 확인 후 생성(MySQL 은 CREATE INDEX IF NOT EXISTS 미지원)
|
||
-- =====================================================================
|
||
SET @idx := (SELECT COUNT(1) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNEMPLYRSCRTYESTBS'
|
||
AND INDEX_NAME = 'IX_EMPLYRSCRTY_TRGET');
|
||
|
||
SET @ddl := IF(@idx > 0,
|
||
'SELECT ''SKIP: IX_EMPLYRSCRTY_TRGET already exists'' AS msg',
|
||
'CREATE INDEX IX_EMPLYRSCRTY_TRGET ON COMTNEMPLYRSCRTYESTBS (SCRTY_DTRMN_TRGET_ID)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
|
||
-- ─────────────────────────────
|
||
-- [2026-08-27 드리프트 보완] 스크립트에 빠져 있던 ESNTL_ID 조회 인덱스 2건
|
||
-- ─────────────────────────────
|
||
-- =====================================================================
|
||
-- [발견 경위] 운영 스키마 덤프(csv_structure_Dump20260520) ↔ 로컬 csv 스키마
|
||
-- 전수 대조 결과, 로컬에만 존재하고 저장소 어느 SQL 파일에도 없는
|
||
-- 인덱스 2개를 발견했다(개발 중 DB에 직접 수실행하고 스크립트화를 누락).
|
||
-- 이 파일만 운영에 돌리면 둘 다 누락돼 로컬에서만 빠른 조회가 된다.
|
||
--
|
||
-- · COMTNEMPLYRINFO(ESNTL_ID) : comvnusermaster/comvnuserinfo 뉴의 USR 분기와
|
||
-- 권한·미사용계정 화면이 ESNTL_ID 로 조인한다.
|
||
-- · BIZ_GROUP(ESNTL_ID) : 동일 뉴의 GRO 분기.
|
||
--
|
||
-- [안전성] 추가(additive) 인덱스라 기존 동작에 영향 없음.
|
||
-- [재실행] information_schema 존재 확인 후 생성 — 반복 실행 무해.
|
||
-- [롤백] DROP INDEX IX_EMPLYR_ESNTL ON COMTNEMPLYRINFO;
|
||
-- DROP INDEX IX_BIZ_GROUP_ESNTL ON BIZ_GROUP;
|
||
-- =====================================================================
|
||
|
||
SET @idx := (SELECT COUNT(1) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'COMTNEMPLYRINFO'
|
||
AND INDEX_NAME = 'IX_EMPLYR_ESNTL');
|
||
SET @ddl := IF(@idx > 0,
|
||
'SELECT ''SKIP: IX_EMPLYR_ESNTL already exists'' AS msg',
|
||
'CREATE INDEX IX_EMPLYR_ESNTL ON COMTNEMPLYRINFO (ESNTL_ID)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
SET @idx := (SELECT COUNT(1) FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_NAME = 'BIZ_GROUP'
|
||
AND INDEX_NAME = 'IX_BIZ_GROUP_ESNTL');
|
||
SET @ddl := IF(@idx > 0,
|
||
'SELECT ''SKIP: IX_BIZ_GROUP_ESNTL already exists'' AS msg',
|
||
'CREATE INDEX IX_BIZ_GROUP_ESNTL ON BIZ_GROUP (ESNTL_ID)'
|
||
);
|
||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.mber_nm_encrypt_create_mysql.sql] (2026-08-11 통합)
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 회원 이름(MBER_NM) 암호화 컬럼 추가 : COMTNGNRLMBER (추가제안 Action4)
|
||
-- [대상] COMTNGNRLMBER 단독(실측 77,053건). biz_mber_ssn/biz_join_event/biz_stat_summary_* 는
|
||
-- 5,000건 기준 미달로 미대상.
|
||
-- [설계] 평문 컬럼 유지 + 별도 컬럼 추가(MBER_NM_ENC/MBER_NM_HASH/CRYPTO_KEY_VER).
|
||
-- MBER_NM_ENC IS NULL 로 미암호화 여부를 판정하므로 이중 암호화 사고가 원천 차단된다.
|
||
-- [검색] AES-GCM 랜덤IV라 부분일치 불가 — 완전일치는 HMAC-SHA256 해시(MBER_NM_HASH)로 별도 지원.
|
||
-- [무중단] ALGORITHM=INPLACE, LOCK=NONE.
|
||
-- ★★ 이 DDL 적용 후, PART 4(뷰 GNR 컷오버) 실행 전에 회원 이름 백필 배치가 반드시
|
||
-- 완료돼야 한다 — /mnt/sec/memberNmCrypto/start.do?confirm=BACKFILL (POST, ROLE_ADMIN)
|
||
-- 로 트리거. PART 4 자체에 미완료 시 자동 SKIP 가드가 있다. ★★
|
||
-- =====================================================================
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_ADD_NM_CRYPTO_COLS;
|
||
DELIMITER $$
|
||
CREATE PROCEDURE PRC_PRIVACY_ADD_NM_CRYPTO_COLS()
|
||
BEGIN
|
||
DECLARE v_cnt INT DEFAULT 0;
|
||
|
||
SELECT COUNT(*) INTO v_cnt FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'COMTNGNRLMBER'
|
||
AND COLUMN_NAME = 'MBER_NM_ENC';
|
||
IF v_cnt = 0 THEN
|
||
ALTER TABLE COMTNGNRLMBER
|
||
ADD COLUMN MBER_NM_ENC VARCHAR(255) NULL
|
||
COMMENT '이름 암호문 Base64(IV12||CT||TAG16), AES-128/GCM',
|
||
ALGORITHM=INPLACE, LOCK=NONE;
|
||
END IF;
|
||
|
||
SELECT COUNT(*) INTO v_cnt FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'COMTNGNRLMBER'
|
||
AND COLUMN_NAME = 'MBER_NM_HASH';
|
||
IF v_cnt = 0 THEN
|
||
ALTER TABLE COMTNGNRLMBER
|
||
ADD COLUMN MBER_NM_HASH CHAR(64) NULL
|
||
COMMENT '이름 완전일치 검색용 HMAC-SHA256(pepper) 소문자 hex',
|
||
ALGORITHM=INPLACE, LOCK=NONE;
|
||
END IF;
|
||
|
||
SELECT COUNT(*) INTO v_cnt FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'COMTNGNRLMBER'
|
||
AND COLUMN_NAME = 'CRYPTO_KEY_VER';
|
||
IF v_cnt = 0 THEN
|
||
ALTER TABLE COMTNGNRLMBER
|
||
ADD COLUMN CRYPTO_KEY_VER TINYINT NULL
|
||
COMMENT '암호화 키 버전(키 교체 대비). 미암호화 행은 NULL',
|
||
ALGORITHM=INPLACE, LOCK=NONE;
|
||
END IF;
|
||
|
||
SELECT COUNT(*) INTO v_cnt FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'COMTNGNRLMBER'
|
||
AND INDEX_NAME = 'IX_GNRLMBER_NM_HASH';
|
||
IF v_cnt = 0 THEN
|
||
CREATE INDEX IX_GNRLMBER_NM_HASH ON COMTNGNRLMBER (MBER_NM_HASH);
|
||
END IF;
|
||
END$$
|
||
DELIMITER ;
|
||
|
||
CALL PRC_PRIVACY_ADD_NM_CRYPTO_COLS();
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_ADD_NM_CRYPTO_COLS;
|
||
|
||
-- 백필 진행상태 테이블. 커서를 잃어도 MBER_NM_ENC IS NULL 조건이 있어 재실행하면 복구된다.
|
||
-- LAST_KEY 가 VARBINARY 인 이유: COMTNGNRLMBER.MBER_ID 가 VARBINARY(32)이고 비인쇄 바이트 포함행 존재.
|
||
CREATE TABLE IF NOT EXISTS MIG_CRYPTO_PROGRESS (
|
||
JOB_ID VARCHAR(50) NOT NULL COMMENT '작업 식별자(예: COMTNGNRLMBER.MBER_NM)',
|
||
LAST_KEY VARBINARY(32) NULL COMMENT '마지막 처리 PK(커서, 원형 바이트). NULL이면 처음부터',
|
||
DONE_CNT BIGINT NOT NULL DEFAULT 0 COMMENT '누적 처리 건수',
|
||
ERR_CNT BIGINT NOT NULL DEFAULT 0 COMMENT '누적 실패 건수',
|
||
JOB_STTUS CHAR(1) NOT NULL DEFAULT 'R' COMMENT 'R:진행 S:중지요청 C:완료 E:오류',
|
||
STR_DT DATETIME NULL COMMENT '시작 일시',
|
||
END_DT DATETIME NULL COMMENT '종료 일시',
|
||
UPDT_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
ON UPDATE CURRENT_TIMESTAMP COMMENT '갱신 일시',
|
||
RM VARCHAR(500) NULL COMMENT '비고(마지막 오류 메시지 등)',
|
||
PRIMARY KEY (JOB_ID)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='개인정보 암호화 이관 배치 진행상태';
|
||
|
||
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- [privacy.mber_nm_dead_views_drop_mysql.sql] (2026-08-11 통합)
|
||
-- ─────────────────────────────────────────────────────────────
|
||
-- =====================================================================
|
||
-- 회원 이름 암호화 — 죽은 뷰 8개 정리
|
||
-- [배경] COMTNGNRLMBER.MBER_NM 참조 뷰 10개 중 comvnuserinfo/comvnusermaster(PART 4 대상)만
|
||
-- 실사용. 나머지 8개는 FROM절 참조 0건인 화석(원래 뷰를 쓰던 기능이 인라인 UNION으로
|
||
-- 대체되어 남은 것) — 그대로 두면 뷰도 GNR 분기 평문을 노출한다.
|
||
-- [백업] 운영 반영 전 `src/script/mysql/backup/privacy.mber_nm_dead_views_backup_20260810.sql`
|
||
-- 로 정의를 먼저 백업해 둘 것(복원 시 그대로 재실행).
|
||
-- [재실행 안전] DROP VIEW IF EXISTS.
|
||
-- ★ [2026-08-27 대소문자 보정] 운영 DB(csv_structure_Dump20260520 실측)는 객체명을
|
||
-- 대문자/혼합 케이스로 저장한다(COMTNBBSUSERINFO, excelAppconfirmList ...). 로컬
|
||
-- Windows MySQL 은 전부 소문자라 종전의 소문자 DROP 문이 로컬에서만 동작하고
|
||
-- 운영에서는 IF EXISTS 때문에 **오류 없이 조용히 아무것도 지우지 않는다**.
|
||
-- → lower_case_table_names 설정에 의존하지 않도록 information_schema 에서
|
||
-- 실제 저장된 이름을 읽어 동적으로 DROP 한다(양쪽 환경 모두 동작).
|
||
-- =====================================================================
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_DROP_DEAD_VIEWS;
|
||
DELIMITER $$
|
||
CREATE PROCEDURE PRC_PRIVACY_DROP_DEAD_VIEWS()
|
||
BEGIN
|
||
DECLARE v_done INT DEFAULT 0;
|
||
DECLARE v_name VARCHAR(64);
|
||
DECLARE cur CURSOR FOR
|
||
SELECT TABLE_NAME FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND LOWER(TABLE_NAME) IN ('comtnbbsuserinfo','comvnusermaster2','comvnusermaster_1',
|
||
'excelappconfirmlist','excelappconfirmlist_auth',
|
||
'selectaddedoptionsinf','selectboardarticlelist',
|
||
'selectmiddleappconfirmlist');
|
||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
|
||
|
||
-- [2026-08-27] DROP 전에 **운영본 정의를 그 DB 안에** 백업한다.
|
||
-- backup/privacy.mber_nm_dead_views_backup_20260810.sql 은 로컬(소문자) 캡처라
|
||
-- 운영 복원에 쓸 수 없다. 롤백 자산은 반드시 운영에서 떠 둬야 한다.
|
||
CREATE TABLE IF NOT EXISTS privacy_view_def_backup (
|
||
view_name VARCHAR(64) NOT NULL,
|
||
backup_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
view_definition MEDIUMTEXT NOT NULL,
|
||
PRIMARY KEY (view_name, backup_at)
|
||
);
|
||
|
||
INSERT INTO privacy_view_def_backup (view_name, view_definition)
|
||
SELECT TABLE_NAME, VIEW_DEFINITION FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND LOWER(TABLE_NAME) IN ('comtnbbsuserinfo','comvnusermaster2','comvnusermaster_1',
|
||
'excelappconfirmlist','excelappconfirmlist_auth',
|
||
'selectaddedoptionsinf','selectboardarticlelist',
|
||
'selectmiddleappconfirmlist')
|
||
AND NOT EXISTS (SELECT 1 FROM privacy_view_def_backup b
|
||
WHERE b.view_name = information_schema.views.TABLE_NAME);
|
||
|
||
OPEN cur;
|
||
drop_loop: LOOP
|
||
FETCH cur INTO v_name;
|
||
IF v_done = 1 THEN LEAVE drop_loop; END IF;
|
||
SET @drop_sql = CONCAT('DROP VIEW IF EXISTS `', v_name, '`');
|
||
PREPARE stmt_dv FROM @drop_sql;
|
||
EXECUTE stmt_dv;
|
||
DEALLOCATE PREPARE stmt_dv;
|
||
SELECT CONCAT('DROPPED dead view (definition backed up): ', v_name) AS DEAD_VIEW_STATUS;
|
||
END LOOP;
|
||
CLOSE cur;
|
||
END$$
|
||
DELIMITER ;
|
||
|
||
CALL PRC_PRIVACY_DROP_DEAD_VIEWS();
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_DROP_DEAD_VIEWS;
|
||
|
||
|
||
-- ─────────────────────────────
|
||
-- [2026-08-27] 신규 utf8mb4 테이블 collation 수렴 (환경간 차이 제거)
|
||
-- ─────────────────────────────
|
||
-- =====================================================================
|
||
-- [문제] `DEFAULT CHARSET=utf8mb4` 만 쓰면 서버 기본값을 따라가서
|
||
-- · 운영 MySQL 5.7 → utf8mb4_general_ci
|
||
-- · 로컬 MySQL 8.0 → utf8mb4_0900_ai_ci (5.7 에는 존재하지도 않는 값)
|
||
-- 로 **같은 스크립트가 환경마다 다른 테이블을 만든다** — 로컬 테스트가
|
||
-- 운영을 정확히 재현하지 못하고, 정렬·비교 의미가 미묘하게 달라진다.
|
||
--
|
||
-- [선택] utf8mb4_unicode_ci — 5.7/8.0 양쪽에 존재하고, 운영의 기존 utf8mb4
|
||
-- 테이블(BIZ_VOLUNTEER 등 13개)이 이미 쓰는 값이라 향후 조인이 생겨도
|
||
-- 'Illegal mix of collations' 가 나지 않는다.
|
||
-- (현재 개인정보보호 SQL 은 utf8mb4 테이블과 조인하지 않음을 실측 확인했으나,
|
||
-- 상대가 utf8mb3 일 때는 MySQL 이 더 넓은 utf8mb4 로 자동 변환하므로 무해하다)
|
||
--
|
||
-- [동작] 위 CREATE TABLE 은 IF NOT EXISTS 라 이미 만들어진 테이블의 collation 을
|
||
-- 바꾸지 못한다. 그래서 여기서 다른 값인 것만 골라 ALTER 한다.
|
||
-- · 운영(신규 설치) : 이미 unicode_ci 로 만들어져 있으므로 **전부 SKIP**
|
||
-- · 로컬(기존 0900_ai_ci) : 해당 테이블만 unicode_ci 로 전환
|
||
-- ※ CHARSET=utf8(utf8mb3) 로 만든 PERSONAL_DATA_ACCESS_LOG / PDAL_PURGE_HISTORY 는
|
||
-- 대상이 아니다(양쪽 모두 utf8mb3_general_ci 로 이미 동일 — 대용량 파티션
|
||
-- 테이블을 불필요하게 재작성하지 않기 위해서도 제외한다).
|
||
-- [재실행] 다른 것만 고르므로 반복 실행 무해(두 번째부터는 전부 SKIP).
|
||
-- =====================================================================
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_ALIGN_COLLATION;
|
||
DELIMITER $$
|
||
CREATE PROCEDURE PRC_PRIVACY_ALIGN_COLLATION()
|
||
BEGIN
|
||
DECLARE v_done INT DEFAULT 0;
|
||
DECLARE v_name VARCHAR(64);
|
||
DECLARE cur CURSOR FOR
|
||
SELECT TABLE_NAME FROM information_schema.TABLES
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND TABLE_TYPE = 'BASE TABLE'
|
||
AND TABLE_COLLATION LIKE 'utf8mb4%'
|
||
AND TABLE_COLLATION <> 'utf8mb4_unicode_ci'
|
||
AND LOWER(TABLE_NAME) IN (
|
||
'access_control_policy','account_action_history','account_delete_history',
|
||
'account_mng_policy','account_unlock_history','author_change_history',
|
||
'author_change_request','author_hist_download_history','author_hist_download_job',
|
||
'conect_ip_location','login_lock_exempt','mber_crypto_access_history',
|
||
'mig_crypto_progress','otp_device','pdal_download_history','pdal_download_job',
|
||
'pdal_inspection_report','privacy_view_def_backup');
|
||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
|
||
|
||
OPEN cur;
|
||
align_loop: LOOP
|
||
FETCH cur INTO v_name;
|
||
IF v_done = 1 THEN LEAVE align_loop; END IF;
|
||
SET @alter_sql = CONCAT('ALTER TABLE `', v_name,
|
||
'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
|
||
PREPARE stmt_col FROM @alter_sql;
|
||
EXECUTE stmt_col;
|
||
DEALLOCATE PREPARE stmt_col;
|
||
SELECT CONCAT('COLLATION aligned -> utf8mb4_unicode_ci : ', v_name) AS COLLATION_STATUS;
|
||
END LOOP;
|
||
CLOSE cur;
|
||
END$$
|
||
DELIMITER ;
|
||
|
||
CALL PRC_PRIVACY_ALIGN_COLLATION();
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_ALIGN_COLLATION;
|
||
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- [privacy.mber_crypto_access_history_create_mysql.sql] 2026-08-27 추가
|
||
-- 암호화 회원정보 관리자 접근 이력(추가제안 Action4 / 제안서 슬라이드16)
|
||
-- · 관리자 전용 회원정보 화면의 복호화 조회·등록·수정·파기 행위를 '사유'와 함께 남긴다.
|
||
-- · 접속기록(PERSONAL_DATA_ACCESS_LOG)이 '언제 어느 화면을 열었나'라면 이 표는 '왜 열어봤나'다.
|
||
-- 둘 다 남기는 것은 중복이 아니라 이중 기록이다(하나가 비면 다른 하나로 대조).
|
||
-- · 개인정보 본문은 적재하지 않는다 — 변경 항목명만 남기고 값은 남기지 않는다.
|
||
-- ---------------------------------------------------------------------
|
||
CREATE TABLE IF NOT EXISTS MBER_CRYPTO_ACCESS_HISTORY
|
||
(
|
||
HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호',
|
||
HANDLR_ID VARCHAR(20) NOT NULL COMMENT '처리자(취급자) 계정ID',
|
||
ACTION_TY CHAR(1) NOT NULL COMMENT '행위 S:복호화조회 I:등록 U:수정 D:개인정보파기',
|
||
TRGET_MBER_ID VARCHAR(20) NULL COMMENT '대상 회원ID(목록 검색은 NULL)',
|
||
ACCESS_REASON VARCHAR(500) NOT NULL COMMENT '사유(필수 입력)',
|
||
CHANGED_ITEMS VARCHAR(500) NULL COMMENT '변경 항목명 목록(값은 미적재). 예: 이름,휴대폰',
|
||
HANDLR_IP VARCHAR(45) NULL COMMENT '처리자 IP(IPv6 대비 45)',
|
||
REAUTH_YN CHAR(1) NOT NULL DEFAULT 'N' COMMENT '재인증 통과 여부',
|
||
ACCESS_DT DATETIME NOT NULL COMMENT '처리 일시',
|
||
PRIMARY KEY (HIST_ID),
|
||
KEY IDX_MCAH_01 (ACCESS_DT),
|
||
KEY IDX_MCAH_02 (HANDLR_ID),
|
||
KEY IDX_MCAH_03 (TRGET_MBER_ID)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||
COMMENT='암호화 회원정보 관리자 접근 이력(사유·재인증, 추가제안 Action4)';
|
||
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- [privacy.access_log_keep_day_add_mysql.sql] 2026-08-27 추가
|
||
-- 접속기록 보관기간 관리자 설정 (SFR-004 / 제안서 slide15 "관리자 설정값 미존재 → 신규 구축")
|
||
-- · 파기 배치가 쓰던 자바 상수 730 을 정책값으로 뺀다.
|
||
-- · 법정 하한 730일(2년) — 화면·서버 검증과 파기 배치 양쪽에서 강제한다.
|
||
-- DB CHECK 를 쓰지 않는 이유는 MariaDB/MySQL 5.7 에서 동작이 갈려 조용히 통과할 수 있어서다.
|
||
-- ---------------------------------------------------------------------
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_ADD_ACCESS_LOG_KEEP_DAY;
|
||
DELIMITER $$
|
||
CREATE PROCEDURE PRC_PRIVACY_ADD_ACCESS_LOG_KEEP_DAY()
|
||
BEGIN
|
||
DECLARE v_cnt INT DEFAULT 0;
|
||
SELECT COUNT(*) INTO v_cnt FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ACCOUNT_MNG_POLICY'
|
||
AND COLUMN_NAME = 'ACCESS_LOG_KEEP_DAY';
|
||
IF v_cnt = 0 THEN
|
||
ALTER TABLE ACCOUNT_MNG_POLICY
|
||
ADD COLUMN ACCESS_LOG_KEEP_DAY INT NOT NULL DEFAULT 730
|
||
COMMENT '개인정보 접속기록 보관기간(일). 법정 하한 730(2년)',
|
||
ALGORITHM=INPLACE, LOCK=NONE;
|
||
END IF;
|
||
END$$
|
||
DELIMITER ;
|
||
CALL PRC_PRIVACY_ADD_ACCESS_LOG_KEEP_DAY();
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_ADD_ACCESS_LOG_KEEP_DAY;
|
||
|
||
UPDATE ACCOUNT_MNG_POLICY SET ACCESS_LOG_KEEP_DAY = 730
|
||
WHERE POLICY_ID = 1 AND (ACCESS_LOG_KEEP_DAY IS NULL OR ACCESS_LOG_KEEP_DAY < 730);
|
||
|
||
|
||
-- =====================================================================
|
||
-- PART 2. DML (기준/시드 데이터) ※ 멱등: NOT EXISTS 가드로 중복 적재 방지
|
||
-- 수록: ACCOUNT_MNG_POLICY(기본정책 1행)
|
||
-- 미수록: OTP_DEVICE / PERSONAL_DATA_ACCESS_LOG / *_HISTORY = 런타임 적재(시드 없음)
|
||
-- [2026-08-11] PDAL_JOB_MAPPING(업무유형 매핑 13행) 시드 제거 — 업무유형 매핑 관리 화면
|
||
-- (메뉴 9100045) 폐기. 접속기록의 수행업무는 MenuAccessLogInterceptor 가
|
||
-- COMTNMENUINFO 메뉴명에서 직접 채우므로 이 시드는 더 이상 소비되지 않는다.
|
||
-- =====================================================================
|
||
|
||
-- 1) ACCOUNT_MNG_POLICY — 장기 미사용 계정 관리 기본정책(단일행 POLICY_ID=1)
|
||
INSERT INTO ACCOUNT_MNG_POLICY (POLICY_ID, DORMANT_DAY, ALERT_D1, ALERT_D2, ALERT_D3, DELETE_PRESERVE_DAY, BATCH_CYCLE, UPD_ID, UPD_DT)
|
||
SELECT 1, 365, 30, 7, 0, 365, 'DAILY', 'SYSTEM', NOW()
|
||
FROM DUAL
|
||
WHERE NOT EXISTS (SELECT 1 FROM ACCOUNT_MNG_POLICY WHERE POLICY_ID = 1);
|
||
|
||
-- (부록) LOGIN_LOCK_COUNT — 기존 arko 테이블(변동분 아님). 미존재 신규 환경에서만 사용(기본 5):
|
||
-- INSERT INTO LOGIN_LOCK_COUNT (LOCK_COUNT)
|
||
-- SELECT 5 FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM LOGIN_LOCK_COUNT);
|
||
|
||
-- 3) CONECT_IP_LOCATION — 접속위치 매핑 기본 시드(개발항목 62)
|
||
-- 운영 대역(본부·전산실·지역문화원 등)은 확정 후 같은 형식으로 INSERT 만 추가한다.
|
||
-- 매칭이 없으면 소스 fallback(루프백/사설/외부)이 적용되므로 비어 있어도 동작한다.
|
||
INSERT INTO CONECT_IP_LOCATION (IP_CIDR, LOC_NM, LOC_SE, SORT_ORDR, USE_AT, REMARK, REGISTER_ID)
|
||
SELECT * FROM (
|
||
SELECT '127.0.0.0/8' AS IP_CIDR, '서버 로컬' AS LOC_NM, 'I' AS LOC_SE, 1 AS SORT_ORDR,
|
||
'Y' AS USE_AT, '루프백 — WAS 자체 호출' AS REMARK, 'SYSTEM' AS REGISTER_ID
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM CONECT_IP_LOCATION x WHERE x.IP_CIDR = seed.IP_CIDR);
|
||
|
||
INSERT INTO CONECT_IP_LOCATION (IP_CIDR, LOC_NM, LOC_SE, SORT_ORDR, USE_AT, REMARK, REGISTER_ID)
|
||
SELECT * FROM (
|
||
SELECT '10.0.0.0/8' AS IP_CIDR, '내부망' AS LOC_NM, 'I' AS LOC_SE, 2 AS SORT_ORDR, 'Y' AS USE_AT, '사설 대역 — 세부 위치는 더 긴 CIDR 로 덮어쓰기' AS REMARK, 'SYSTEM' AS REGISTER_ID
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM CONECT_IP_LOCATION x WHERE x.IP_CIDR = seed.IP_CIDR);
|
||
|
||
INSERT INTO CONECT_IP_LOCATION (IP_CIDR, LOC_NM, LOC_SE, SORT_ORDR, USE_AT, REMARK, REGISTER_ID)
|
||
SELECT * FROM (
|
||
SELECT '172.16.0.0/12' AS IP_CIDR, '내부망' AS LOC_NM, 'I' AS LOC_SE, 3 AS SORT_ORDR, 'Y' AS USE_AT, '사설 대역' AS REMARK, 'SYSTEM' AS REGISTER_ID
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM CONECT_IP_LOCATION x WHERE x.IP_CIDR = seed.IP_CIDR);
|
||
|
||
INSERT INTO CONECT_IP_LOCATION (IP_CIDR, LOC_NM, LOC_SE, SORT_ORDR, USE_AT, REMARK, REGISTER_ID)
|
||
SELECT * FROM (
|
||
SELECT '192.168.0.0/16' AS IP_CIDR, '내부망' AS LOC_NM, 'I' AS LOC_SE, 4 AS SORT_ORDR, 'Y' AS USE_AT, '사설 대역' AS REMARK, 'SYSTEM' AS REGISTER_ID
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM CONECT_IP_LOCATION x WHERE x.IP_CIDR = seed.IP_CIDR);
|
||
|
||
-- =====================================================================
|
||
-- PART 3. 관리자 메뉴 등록 DML (좌측/상단 메뉴 노출)
|
||
-- 구성: 권한관리(9100030) 하위에 MENU_ORDR 순으로 등록
|
||
-- [1]~[5] 9100035~9100039 권한 신청·승인 / 권한 변경 이력 / 미사용계정 현황 /
|
||
-- 계정 관리 정책 / 인증 실패 이력
|
||
-- [6] 9100047(ORDR 13) 접근통제 정책
|
||
-- [7] 9100044 명칭 정리("접속기록 조회 및 감사") + 9100051(ORDR 17) 회원정보 암호화 관리
|
||
-- + 중간 작업에서 만들었다가 내린 메뉴(9100048/49/50/52/53) 정리
|
||
-- ※ 접속기록 조회(9100044, ORDR 10)의 등록 자체는 위 [1]~[5] 블록에 들어 있다.
|
||
-- 원본: src/script/mysql/dml/privacy.menu_author_request_insert_mysql.sql
|
||
-- src/script/mysql/dml/privacy.menu_author_history_insert_mysql.sql
|
||
-- src/script/mysql/dml/privacy.menu_dormant_status_insert_mysql.sql
|
||
-- src/script/mysql/dml/privacy.menu_account_policy_insert_mysql.sql
|
||
-- src/script/mysql/dml/privacy.menu_access_log_insert_mysql.sql
|
||
-- src/script/mysql/dml/privacy.menu_access_policy_insert_mysql.sql
|
||
-- src/script/mysql/dml/privacy.menu_privacy_screens4_insert_mysql.sql
|
||
-- (개별 파일에 화면별 배치 근거·롤백 SQL이 더 상세히 있음 — 여기서는 통합 실행용)
|
||
--
|
||
-- ★★ 이 DML만으로는 좌측메뉴에 안 뜬다 — left.jsp 수정 배포 필수 ★★
|
||
-- src/main/webapp/WEB-INF/jsp/site/mnt/left.jsp 는 메뉴번호 하드코딩 JSP라
|
||
-- 권한관리(9100030) 블록에 등록한 메뉴번호마다 <c:when> 분기 +
|
||
-- 시스템관리/권한관리 상위 하이라이트 조건(param.page eq '91000xx')이
|
||
-- 반드시 함께 배포돼야 렌더된다. (arko_renual 소스에는 9100035~9100039·9100044·
|
||
-- 9100047·9100051 전부 이미 반영 완료 — 운영 배포 시 WAR/배포본에 이 JSP가
|
||
-- 포함됐는지만 확인하면 됨)
|
||
--
|
||
-- [재실행 안전] 모든 INSERT NOT EXISTS 가드 → 반복 실행 무해.
|
||
-- [노출권한] 전 건 IS_AUTHENTICATED_FULLY, ROLE_ADMIN 동일
|
||
-- (실제 접근 차단은 urlRoleMapping 의 /mnt/sec/* = ROLE_ADMIN 이 담당)
|
||
-- [Oracle] 대상 운영 DB가 MySQL(csv)이라 Oracle 쌍 미작성
|
||
-- =====================================================================
|
||
|
||
-- ── [1] 프로그램(URL) 등록 (COMTNMENUINFO.PROGRM_FILE_NM → COMTNPROGRMLIST FK, 메뉴보다 먼저) ──
|
||
INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL)
|
||
SELECT * FROM (
|
||
SELECT 'AuthorRequestList' AS PROGRM_FILE_NM, '/mnt/sec/authorRequest/' AS PROGRM_STRE_PATH, '개인정보취급자 권한 관리' AS PROGRM_KOREAN_NM, '개인정보취급자 권한 관리(SFR-001 #46)' AS PROGRM_DC, '/mnt/sec/authorRequest/listView.do?page=9100035' AS URL UNION ALL
|
||
SELECT 'AuthorHistoryList', '/mnt/sec/authorHistory/', '권한변경 이력조회', '권한변경 이력조회·검색·다운로드(SFR-001 #6)', '/mnt/sec/authorHistory/listView.do?page=9100036' UNION ALL
|
||
SELECT 'DormantStatusList', '/mnt/sec/dormantStatus/', '장기 미사용 관리자 계정 현황', '장기 미사용 관리자 계정 현황 조회(SFR-002 #11)', '/mnt/sec/dormantStatus/listView.do?page=9100037' UNION ALL
|
||
SELECT 'AccountPolicySet', '/mnt/sec/accountPolicy/', '계정 관리 정책 설정', '장기 미사용 계정 관리 정책 설정(SFR-002 #13)', '/mnt/sec/accountPolicy/policyView.do?page=9100038'
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM);
|
||
|
||
-- ── [2] 메뉴 등록 : 권한관리(9100030) 하위, MENU_ORDR 5~8 순서 ──
|
||
INSERT INTO COMTNMENUINFO (MENU_NM, PROGRM_FILE_NM, MENU_NO, UPPER_MENU_NO, MENU_ORDR, MENU_DC, RELATE_IMAGE_PATH, RELATE_IMAGE_NM)
|
||
SELECT * FROM (
|
||
SELECT '개인정보취급자 권한 관리' AS MENU_NM, 'AuthorRequestList' AS PROGRM_FILE_NM, 9100035 AS MENU_NO, 9100030 AS UPPER_MENU_NO, 5 AS MENU_ORDR, '개인정보취급자 권한 부여·변경·말소 현황 및 승인 처리' AS MENU_DC, '/' AS RELATE_IMAGE_PATH, '/' AS RELATE_IMAGE_NM UNION ALL
|
||
SELECT '권한변경 이력조회', 'AuthorHistoryList', 9100036, 9100030, 6, '권한변경 이력조회·검색·다운로드', '/', '/' UNION ALL
|
||
SELECT '미사용 관리자 계정 현황', 'DormantStatusList', 9100037, 9100030, 7, '장기 미사용 관리자 계정 현황 조회', '/', '/' UNION ALL
|
||
SELECT '계정 관리 정책', 'AccountPolicySet', 9100038, 9100030, 8, '장기 미사용 계정 관리 정책 설정', '/', '/'
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNMENUINFO m WHERE m.MENU_NO = seed.MENU_NO);
|
||
|
||
-- ── [2-1] 명칭 정정(2026-08-10, 재실행 안전/멱등) — 9100037 이 관리자(EMP) 전용 화면임을 명시.
|
||
-- 이미 9100037 이 위 INSERT 로 적재된 환경은 NOT EXISTS 가드 때문에 INSERT 값이
|
||
-- 반영되지 않으므로 이 UPDATE 가 실제 적용 지점이다. 한글 리터럴은 UNHEX 우회 권장
|
||
-- (python3 -c "print('미사용 관리자 계정 현황'.encode('utf-8').hex().upper())").
|
||
UPDATE COMTNMENUINFO SET MENU_NM = '미사용 관리자 계정 현황' WHERE MENU_NO = 9100037;
|
||
UPDATE COMTNPROGRMLIST SET PROGRM_KOREAN_NM = '장기 미사용 관리자 계정 현황',
|
||
PROGRM_DC = '장기 미사용 관리자 계정 현황 조회(SFR-002 #11)'
|
||
WHERE PROGRM_FILE_NM = 'DormantStatusList';
|
||
|
||
-- ── [3] 권한↔메뉴 노출 매핑 (4건 × 2권한, FK 안전) ──
|
||
INSERT INTO COMTNMENUCREATDTLS (MENU_NO, AUTHOR_CODE)
|
||
SELECT seed.menu_no, g.ac
|
||
FROM (SELECT 9100035 AS menu_no UNION ALL SELECT 9100036 UNION ALL SELECT 9100037 UNION ALL SELECT 9100038) seed
|
||
CROSS JOIN (SELECT 'IS_AUTHENTICATED_FULLY' AS ac UNION ALL SELECT 'ROLE_ADMIN') g
|
||
WHERE g.ac IN (SELECT AUTHOR_CODE FROM COMTNAUTHORINFO) -- FK 안전
|
||
AND NOT EXISTS (SELECT 1 FROM COMTNMENUCREATDTLS x
|
||
WHERE x.MENU_NO = seed.menu_no AND x.AUTHOR_CODE = g.ac);
|
||
|
||
-- ── [4] 인증 실패 이력 조회 메뉴(9100039) — SER-001 #22-1, 2026-08-05 추가 ──
|
||
-- 원본: src/script/mysql/dml/privacy.menu_login_fail_history_insert_mysql.sql
|
||
-- ★ left.jsp 9100039 <c:when> 분기 동반 배포 필수(위와 동일 제약)
|
||
INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL)
|
||
SELECT * FROM (
|
||
SELECT 'LoginFailHistoryList' AS PROGRM_FILE_NM, '/mnt/sec/loginFailHistory/' AS PROGRM_STRE_PATH,
|
||
'인증 실패 이력' AS PROGRM_KOREAN_NM, '로그인 인증 실패 이력 조회(SER-001 #22-1)' AS PROGRM_DC,
|
||
'/mnt/sec/loginFailHistory/listView.do?page=9100039' AS URL
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM);
|
||
|
||
INSERT INTO COMTNMENUINFO (MENU_NM, PROGRM_FILE_NM, MENU_NO, UPPER_MENU_NO, MENU_ORDR, MENU_DC, RELATE_IMAGE_PATH, RELATE_IMAGE_NM)
|
||
SELECT * FROM (
|
||
SELECT '인증 실패 이력' AS MENU_NM, 'LoginFailHistoryList' AS PROGRM_FILE_NM, 9100039 AS MENU_NO,
|
||
9100030 AS UPPER_MENU_NO, 9 AS MENU_ORDR, '로그인 인증 실패 이력 조회' AS MENU_DC,
|
||
'/' AS RELATE_IMAGE_PATH, '/' AS RELATE_IMAGE_NM
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNMENUINFO m WHERE m.MENU_NO = seed.MENU_NO);
|
||
|
||
INSERT INTO COMTNMENUCREATDTLS (MENU_NO, AUTHOR_CODE)
|
||
SELECT 9100039, g.ac
|
||
FROM (SELECT 'IS_AUTHENTICATED_FULLY' AS ac UNION ALL SELECT 'ROLE_ADMIN') g
|
||
WHERE g.ac IN (SELECT AUTHOR_CODE FROM COMTNAUTHORINFO)
|
||
AND NOT EXISTS (SELECT 1 FROM COMTNMENUCREATDTLS x
|
||
WHERE x.MENU_NO = 9100039 AND x.AUTHOR_CODE = g.ac);
|
||
|
||
-- ── [5] 개인정보 접속기록 조회·다운로드 메뉴(9100044) — SFR-004 #47, 2026-08-06 추가 ──
|
||
-- 원본: src/script/mysql/dml/privacy.menu_access_log_insert_mysql.sql
|
||
-- ★ left.jsp 9100044 <c:when> 분기 + 하이라이트 조건 4곳 동반 배포 필수(위와 동일 제약)
|
||
-- ※ 9100040~9100043 은 '게시판관리' 그룹이 선점 → 미사용 번호 9100044 사용(MENU_ORDR 10)
|
||
INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL)
|
||
SELECT * FROM (
|
||
SELECT 'AccessLogList' AS PROGRM_FILE_NM, '/mnt/sec/accessLog/' AS PROGRM_STRE_PATH,
|
||
'개인정보 접속기록' AS PROGRM_KOREAN_NM, '개인정보 접속기록 조회·다운로드(SFR-004 #47)' AS PROGRM_DC,
|
||
'/mnt/sec/accessLog/listView.do?page=9100044' AS URL
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM);
|
||
|
||
INSERT INTO COMTNMENUINFO (MENU_NM, PROGRM_FILE_NM, MENU_NO, UPPER_MENU_NO, MENU_ORDR, MENU_DC, RELATE_IMAGE_PATH, RELATE_IMAGE_NM)
|
||
SELECT * FROM (
|
||
SELECT '개인정보 접속기록' AS MENU_NM, 'AccessLogList' AS PROGRM_FILE_NM, 9100044 AS MENU_NO,
|
||
9100030 AS UPPER_MENU_NO, 10 AS MENU_ORDR, '개인정보 접속기록 조회·다운로드' AS MENU_DC,
|
||
'/' AS RELATE_IMAGE_PATH, '/' AS RELATE_IMAGE_NM
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNMENUINFO m WHERE m.MENU_NO = seed.MENU_NO);
|
||
|
||
INSERT INTO COMTNMENUCREATDTLS (MENU_NO, AUTHOR_CODE)
|
||
SELECT 9100044, g.ac
|
||
FROM (SELECT 'IS_AUTHENTICATED_FULLY' AS ac UNION ALL SELECT 'ROLE_ADMIN') g
|
||
WHERE g.ac IN (SELECT AUTHOR_CODE FROM COMTNAUTHORINFO)
|
||
AND NOT EXISTS (SELECT 1 FROM COMTNMENUCREATDTLS x
|
||
WHERE x.MENU_NO = 9100044 AND x.AUTHOR_CODE = g.ac);
|
||
|
||
-- ── [6] 접근통제 정책 설정 메뉴(9100047) — SER-001 요건 4·5, 2026-08-10 추가(2026-08-11 통합) ──
|
||
-- 원본: src/script/mysql/dml/privacy.menu_access_policy_insert_mysql.sql
|
||
-- ★ left.jsp 9100047 <c:when> 분기 + 하이라이트 조건 4곳 동반 배포 필수(위와 동일 제약)
|
||
-- [선행] 위 PART 1의 ACCESS_CONTROL_POLICY 가 먼저 생성돼 있어야 화면 진입 시 기본값 표시
|
||
INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL)
|
||
SELECT 'AccessPolicySet', '/mnt/sec/accessPolicy/', '접근통제 정책 설정',
|
||
'계정 잠금·자동 로그아웃·세션 재발급 정책 설정(SER-001 요건 4·5)', '/mnt/sec/accessPolicy/policyView.do?page=9100047'
|
||
FROM DUAL
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST WHERE PROGRM_FILE_NM = 'AccessPolicySet');
|
||
|
||
INSERT INTO COMTNMENUINFO (MENU_NM, PROGRM_FILE_NM, MENU_NO, UPPER_MENU_NO, MENU_ORDR, MENU_DC, RELATE_IMAGE_PATH, RELATE_IMAGE_NM)
|
||
SELECT '접근통제 정책', 'AccessPolicySet', 9100047, 9100030, 13, '계정 잠금·자동 로그아웃·세션 재발급 정책 설정', '/', '/'
|
||
FROM DUAL
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNMENUINFO WHERE MENU_NO = 9100047);
|
||
|
||
INSERT INTO COMTNMENUCREATDTLS (MENU_NO, AUTHOR_CODE)
|
||
SELECT 9100047, g.ac
|
||
FROM (SELECT 'IS_AUTHENTICATED_FULLY' AS ac UNION ALL SELECT 'ROLE_ADMIN') g
|
||
WHERE g.ac IN (SELECT AUTHOR_CODE FROM COMTNAUTHORINFO)
|
||
AND NOT EXISTS (SELECT 1 FROM COMTNMENUCREATDTLS x
|
||
WHERE x.MENU_NO = 9100047 AND x.AUTHOR_CODE = g.ac);
|
||
|
||
-- ── [검증] ──
|
||
-- SELECT m.MENU_NO,m.UPPER_MENU_NO,m.MENU_NM,m.MENU_ORDR,p.URL,
|
||
-- (SELECT COUNT(*) FROM COMTNMENUCREATDTLS d WHERE d.MENU_NO=m.MENU_NO) grants
|
||
-- FROM COMTNMENUINFO m JOIN COMTNPROGRMLIST p ON m.PROGRM_FILE_NM=p.PROGRM_FILE_NM
|
||
-- WHERE m.MENU_NO IN (9100035,9100036,9100037,9100038,9100039,9100044,9100047) ORDER BY m.MENU_ORDR;
|
||
|
||
-- ── [롤백] (역순 권장, 4건 일괄) ──
|
||
-- DELETE FROM COMTNMENUCREATDTLS WHERE MENU_NO BETWEEN 9100035 AND 9100038;
|
||
-- DELETE FROM COMTNMENUINFO WHERE MENU_NO BETWEEN 9100035 AND 9100038;
|
||
-- DELETE FROM COMTNPROGRMLIST WHERE PROGRM_FILE_NM IN
|
||
-- ('AuthorRequestList','AuthorHistoryList','DormantStatusList','AccountPolicySet');
|
||
|
||
|
||
-- ── [7] 개인정보보호 화면 메뉴 최종형 (2026-08-27) ──
|
||
-- [확정 근거] 제안요청서 과업내용 "다. 개인정보취급자 접속 및 처리기록 관리 기능 개발" 이
|
||
-- 요구하는 화면은 하나다 — "관리자용 로그 조회 화면 개발".
|
||
-- 제안요약서 slide 14~15 의 시안도 "접속기록 조회 및 감사" 한 장만 그려져 있다.
|
||
-- 따라서 접속·처리기록 영역은 **메뉴 1개**로 끝난다.
|
||
--
|
||
-- [그 한 화면 안에서 제공하는 것] — RFP 가 '기능'으로 명시한 항목들이라 지우지 않는다
|
||
-- · "처리기록 검색·조회 기능 제공" → 처리구분(조회/등록/수정/삭제) 필터
|
||
-- · "비정상 접근 탐지 및 기록 기능 구현" → 목록의 '감사' 칸(이상/점검) 상시 표시
|
||
-- · SFR-004 "접속기록 정기점검을 지원" → 월 1회 배치 + PDAL_INSPECTION_REPORT 적재
|
||
-- · 파기이력(SER-002-2) → 화면 내 "파기이력" 버튼
|
||
--
|
||
-- [이 블록이 하는 일]
|
||
-- (1) 9100044 를 "접속기록 조회 및 감사" 로 정리(권한관리 9100030 하위, ORDR 10)
|
||
-- (2) 9100051 회원정보 암호화 관리(추가제안 Action4) 메뉴 등록
|
||
-- (3) 2026-08-27 중간 작업에서 만들었다가 시안 대조 후 내린 메뉴 정리
|
||
-- (9100048 정기점검 / 9100049 이상행위 / 9100050 파기이력 /
|
||
-- 9100052 그룹 / 9100053 처리기록) — 이미 적용한 DB 를 위한 정리구문이다.
|
||
-- 신규 설치라면 해당 행이 없으므로 아무 일도 일어나지 않는다.
|
||
--
|
||
-- ★ left.jsp 동반 배포 필수 — 권한관리 블록에 9100044·9100051 분기 + 하이라이트 조건
|
||
-- [재실행 안전] INSERT 는 NOT EXISTS, UPDATE 는 같은 값으로 수렴, DELETE 는 대상 한정.
|
||
|
||
-- (1) 접속기록 화면 명칭 정리
|
||
UPDATE COMTNMENUINFO
|
||
SET UPPER_MENU_NO = 9100030, MENU_ORDR = 10,
|
||
MENU_NM = '접속기록 조회 및 감사',
|
||
MENU_DC = '개인정보 접속·처리기록 조회, 이상행위 감사, 다운로드(사유·이력)'
|
||
WHERE MENU_NO = 9100044;
|
||
|
||
UPDATE COMTNPROGRMLIST
|
||
SET PROGRM_KOREAN_NM = '접속기록 조회 및 감사',
|
||
PROGRM_DC = '개인정보 접속·처리기록 조회 및 감사(RFP 과업내용 다 / SFR-004)'
|
||
WHERE PROGRM_FILE_NM = 'AccessLogList';
|
||
|
||
-- (2) 회원정보 암호화 관리 (추가제안 Action4)
|
||
INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL)
|
||
SELECT * FROM (
|
||
SELECT 'MberCryptoAdmin' AS PROGRM_FILE_NM, '/mnt/sec/mberCrypto/' AS PROGRM_STRE_PATH,
|
||
'회원정보 암호화 관리' AS PROGRM_KOREAN_NM,
|
||
'암호화 회원정보 복호화 조회·수정·파기·등록(추가제안 Action4)' AS PROGRM_DC,
|
||
'/mnt/sec/mberCrypto/listView.do?page=9100051' AS URL
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM);
|
||
|
||
INSERT INTO COMTNMENUINFO (MENU_NM, PROGRM_FILE_NM, MENU_NO, UPPER_MENU_NO, MENU_ORDR, MENU_DC, RELATE_IMAGE_PATH, RELATE_IMAGE_NM)
|
||
SELECT * FROM (
|
||
SELECT '회원정보 암호화 관리' AS MENU_NM, 'MberCryptoAdmin' AS PROGRM_FILE_NM, 9100051 AS MENU_NO,
|
||
9100030 AS UPPER_MENU_NO, 17 AS MENU_ORDR, '암호화 회원정보 복호화 CRUD(사유·재인증)' AS MENU_DC,
|
||
'/' AS RELATE_IMAGE_PATH, '/' AS RELATE_IMAGE_NM
|
||
) seed
|
||
WHERE NOT EXISTS (SELECT 1 FROM COMTNMENUINFO m WHERE m.MENU_NO = seed.MENU_NO);
|
||
|
||
INSERT INTO COMTNMENUCREATDTLS (MENU_NO, AUTHOR_CODE)
|
||
SELECT 9100051, g.ac
|
||
FROM (SELECT 'IS_AUTHENTICATED_FULLY' AS ac UNION ALL SELECT 'ROLE_ADMIN') g
|
||
WHERE g.ac IN (SELECT AUTHOR_CODE FROM COMTNAUTHORINFO)
|
||
AND NOT EXISTS (SELECT 1 FROM COMTNMENUCREATDTLS x
|
||
WHERE x.MENU_NO = 9100051 AND x.AUTHOR_CODE = g.ac);
|
||
|
||
-- (3) 중간 작업에서 만들었다가 내린 메뉴 정리 (화면·컨트롤러는 유지/삭제되었음)
|
||
DELETE FROM COMTNMENUCREATDTLS WHERE MENU_NO IN (9100048, 9100049, 9100050, 9100052, 9100053);
|
||
DELETE FROM COMTNMENUINFO WHERE MENU_NO IN (9100048, 9100049, 9100050, 9100052, 9100053);
|
||
|
||
-- [검증] SELECT MENU_NO,UPPER_MENU_NO,MENU_ORDR,MENU_NM,HEX(MENU_NM)
|
||
-- FROM COMTNMENUINFO WHERE MENU_NO IN (9100044,9100051) ORDER BY MENU_ORDR;
|
||
|
||
|
||
-- =====================================================================
|
||
-- PART 4. 뷰 GNR 컷오버 — comvnusermaster / comvnuserinfo (2026-08-11 통합)
|
||
-- 원본: src/script/mysql/ddl/privacy.mber_nm_view_hash_cutover_mysql.sql
|
||
-- 추가제안 Action4(회원정보 암호화)의 마지막 단계. GNR 분기를 평문(MBER_NM)에서
|
||
-- 암호문(MBER_NM_ENC)으로 바꾸고 검색용 USER_NM_HASH 컬럼을 추가한다.
|
||
--
|
||
-- ★★ all-or-nothing 게이트 — 이 PART는 자동으로 스스로를 지킨다 ★★
|
||
-- 아래에서 실제 뷰 재정의를 실행하기 '전에' 두 가지를 확인한다:
|
||
-- ① comvnusermaster 에 USER_NM_HASH 가 이미 있으면(=이미 컷오버됨) SKIP
|
||
-- ② COMTNGNRLMBER 에 MBER_NM_ENC IS NULL 인 행이 하나라도 있으면(=백필 미완료) SKIP
|
||
-- → 이 경우 /mnt/sec/memberNmCrypto/start.do?confirm=BACKFILL(POST, ROLE_ADMIN)로
|
||
-- 백필을 먼저 끝내고 이 install_mysql.sql 을 다시 실행하면 된다(재실행 안전).
|
||
-- 두 조건을 모두 통과했을 때만 실제 CREATE OR REPLACE VIEW 를 수행한다 — 이 파일 하나만
|
||
-- "필요할 때마다" 반복 실행하는 것이 정상 사용법이다(설치 1회 + 백필 후 재실행 1회, 2번 실행).
|
||
--
|
||
-- [기법] information_schema.views.VIEW_DEFINITION 을 서버사이드 REPLACE()로 최소 치환
|
||
-- (PREPARE/EXECUTE 동적 SQL) — comvnuserinfo 의 한글 리터럴(USER_GUBUN_NM 등)을 클라이언트
|
||
-- 에서 절대 재입력하지 않아 셸→mysql.exe stdin 이중 인코딩 문제를 원천 회피한다.
|
||
-- [백업] 실행 전 원본 정의를 privacy_view_def_backup 테이블에 저장(롤백은 그 값으로
|
||
-- CREATE OR REPLACE VIEW 재실행).
|
||
-- =====================================================================
|
||
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_MBER_NM_VIEW_CUTOVER;
|
||
DELIMITER $$
|
||
CREATE PROCEDURE PRC_PRIVACY_MBER_NM_VIEW_CUTOVER()
|
||
BEGIN
|
||
DECLARE v_pending INT DEFAULT 0;
|
||
DECLARE v_done INT DEFAULT 0;
|
||
|
||
SELECT COUNT(*) INTO v_pending FROM COMTNGNRLMBER WHERE MBER_NM_ENC IS NULL;
|
||
SELECT COUNT(*) INTO v_done FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'comvnusermaster' AND COLUMN_NAME = 'USER_NM_HASH';
|
||
|
||
IF v_done > 0 THEN
|
||
SELECT 'SKIP: comvnusermaster.USER_NM_HASH 이미 존재 — 뷰 컷오버 기완료' AS PART4_GATE_STATUS;
|
||
ELSEIF v_pending > 0 THEN
|
||
SELECT CONCAT('SKIP: 백필 미완료 — COMTNGNRLMBER 미암호화 ', v_pending,
|
||
'건 남음. /mnt/sec/memberNmCrypto/start.do?confirm=BACKFILL 로 먼저 완료 후',
|
||
' 이 파일을 재실행할 것') AS PART4_GATE_STATUS;
|
||
ELSE
|
||
SELECT 'PROCEED: 백필 완료 확인(미암호화 0건) — 뷰 컷오버 진행' AS PART4_GATE_STATUS;
|
||
|
||
-- 0. 백업: 현재 뷰 정의를 별도 테이블에 저장(롤백용)
|
||
CREATE TABLE IF NOT EXISTS privacy_view_def_backup (
|
||
view_name VARCHAR(64) NOT NULL,
|
||
backup_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
view_definition MEDIUMTEXT NOT NULL,
|
||
PRIMARY KEY (view_name, backup_at)
|
||
);
|
||
|
||
INSERT INTO privacy_view_def_backup (view_name, view_definition)
|
||
SELECT 'comvnusermaster', VIEW_DEFINITION
|
||
FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'comvnusermaster';
|
||
|
||
INSERT INTO privacy_view_def_backup (view_name, view_definition)
|
||
SELECT 'comvnuserinfo', VIEW_DEFINITION
|
||
FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'comvnuserinfo';
|
||
|
||
-- **★★ [2026-08-27 대소문자 보정] ★★**
|
||
-- REPLACE() 는 lower_case_table_names 설정과 무관하게 **항상 대소문자를 구분**한다.
|
||
-- 운영 DB(csv_structure_Dump20260520 실측)의 VIEW_DEFINITION 은 테이블명이 대문자
|
||
-- (`COMTNGNRLMBER`)인데, 종전 코드는 소문자(`comtngnrlmber`)를 찾고 있었다.
|
||
-- -> 운영에서 5개 REPLACE 가 전부 불발되고, 바뀌지 않은 정의로
|
||
-- CREATE OR REPLACE VIEW 가 **에러 없이 성공**해 USER_NM_HASH 없는 뉴가 남는다.
|
||
-- 아래처럼 실제 저장된 객체명을 information_schema 에서 읽어 검색문자열을 조립한다.
|
||
SET @n_gnr = NULL; SET @n_emp = NULL; SET @n_ent = NULL; SET @n_grp = NULL;
|
||
SET @v_master = NULL; SET @v_info = NULL;
|
||
|
||
SELECT TABLE_NAME INTO @n_gnr FROM information_schema.TABLES
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comtngnrlmber' LIMIT 1;
|
||
SELECT TABLE_NAME INTO @n_emp FROM information_schema.TABLES
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comtnemplyrinfo' LIMIT 1;
|
||
SELECT TABLE_NAME INTO @n_ent FROM information_schema.TABLES
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comtnentrprsmber' LIMIT 1;
|
||
SELECT TABLE_NAME INTO @n_grp FROM information_schema.TABLES
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'biz_group' LIMIT 1;
|
||
SELECT TABLE_NAME INTO @v_master FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comvnusermaster' LIMIT 1;
|
||
SELECT TABLE_NAME INTO @v_info FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comvnuserinfo' LIMIT 1;
|
||
|
||
IF @n_gnr IS NULL OR @n_emp IS NULL OR @n_ent IS NULL OR @n_grp IS NULL
|
||
OR @v_master IS NULL OR @v_info IS NULL THEN
|
||
SIGNAL SQLSTATE '45000'
|
||
SET MESSAGE_TEXT = 'PART4 ABORT: base table or view not found (COMTNGNRLMBER/COMTNEMPLYRINFO/COMTNENTRPRSMBER/BIZ_GROUP/COMVNUSERMASTER/COMVNUSERINFO)';
|
||
END IF;
|
||
|
||
-- 1. comvnusermaster 재정의
|
||
-- GNR 분기: MBER_NM(평문) -> MBER_NM_ENC(암호문), USER_NM_HASH 컴럼 추가(MBER_NM_HASH)
|
||
-- USR/ENT/GRO 분기: USER_NM_HASH 컴럼 추가(NULL - 암호화 대상 아님)
|
||
|
||
-- **★★ [2026-08-27] 스키마 한정자 제거 ★★**
|
||
-- information_schema.views.VIEW_DEFINITION 은 테이블을 항상
|
||
-- `csv`.`COMTNGNRLMBER` 처럼 **스키마 한정자를 붙여** 저장한다
|
||
-- (mysqldump 출력에는 없어서 덤프로 검증하면 몰라보인다).
|
||
-- 그래서 'from `COMTNGNRLMBER` where' 같은 검색문자열은
|
||
-- 실제 텍스트 'from `csv`.`COMTNGNRLMBER` where' 와 맞지 않는다.
|
||
-- → 치환 전에 한정자를 제거한다. CREATE OR REPLACE VIEW 시
|
||
-- MySQL 이 다시 붙이므로 결과물은 동일하다.
|
||
SET @qual = CONCAT('`', DATABASE(), '`.');
|
||
|
||
SET @def_master = (
|
||
SELECT VIEW_DEFINITION FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comvnusermaster'
|
||
);
|
||
SET @def_master = REPLACE(@def_master, @qual, '');
|
||
|
||
SET @def_master = REPLACE(@def_master,
|
||
CONCAT('`', @n_gnr, '`.`MBER_NM` AS `USER_NM`'),
|
||
CONCAT('`', @n_gnr, '`.`MBER_NM_ENC` AS `USER_NM`'));
|
||
SET @def_master = REPLACE(@def_master,
|
||
CONCAT(' end) AS `ADULT` from `', @n_gnr, '` where'),
|
||
CONCAT(' end) AS `ADULT`,`', @n_gnr, '`.`MBER_NM_HASH` AS `USER_NM_HASH` from `', @n_gnr, '` where'));
|
||
SET @def_master = REPLACE(@def_master,
|
||
CONCAT('\'', 'N', '\'', ' AS `ADULT` from `', @n_emp, '` union all'),
|
||
CONCAT('\'', 'N', '\'', ' AS `ADULT`,NULL AS `USER_NM_HASH` from `', @n_emp, '` union all'));
|
||
SET @def_master = REPLACE(@def_master,
|
||
CONCAT('\'', 'N', '\'', ' AS `ADULT` from `', @n_ent, '` where'),
|
||
CONCAT('\'', 'N', '\'', ' AS `ADULT`,NULL AS `USER_NM_HASH` from `', @n_ent, '` where'));
|
||
SET @def_master = REPLACE(@def_master,
|
||
CONCAT('\'', 'N', '\'', ' AS `ADULT` from `', @n_grp, '` where'),
|
||
CONCAT('\'', 'N', '\'', ' AS `ADULT`,NULL AS `USER_NM_HASH` from `', @n_grp, '` where'));
|
||
|
||
SELECT
|
||
(LENGTH(@def_master) - LENGTH(REPLACE(@def_master, 'USER_NM_HASH', ''))) / LENGTH('USER_NM_HASH') AS hash_column_occurrences,
|
||
(@def_master LIKE '%MBER_NM_ENC%') AS gnr_uses_enc_column,
|
||
(@def_master NOT LIKE CONCAT('%`', @n_gnr, '`.`MBER_NM` AS `USER_NM`%')) AS old_plaintext_reference_removed;
|
||
|
||
-- ★ 하드 게이트: 치환이 하나라도 불발되면 여기서 중단한다.
|
||
-- (종전에는 위 SELECT 가 '보고용'이라 불발돼도 그대로 진행 -> 깨진 뉴가 남았다)
|
||
IF (LENGTH(@def_master) - LENGTH(REPLACE(@def_master, 'USER_NM_HASH', ''))) / LENGTH('USER_NM_HASH') <> 4
|
||
OR @def_master NOT LIKE '%MBER_NM_ENC%'
|
||
OR @def_master LIKE CONCAT('%`', @n_gnr, '`.`MBER_NM` AS `USER_NM`%') THEN
|
||
SIGNAL SQLSTATE '45000'
|
||
SET MESSAGE_TEXT = 'PART4 ABORT: comvnusermaster rewrite incomplete (expected 4 USER_NM_HASH + MBER_NM_ENC + no plaintext MBER_NM). View NOT replaced.';
|
||
END IF;
|
||
|
||
SET @sql_master = CONCAT('CREATE OR REPLACE VIEW `', @v_master, '` AS ', @def_master);
|
||
PREPARE stmt_master FROM @sql_master;
|
||
EXECUTE stmt_master;
|
||
DEALLOCATE PREPARE stmt_master;
|
||
|
||
-- 2. comvnuserinfo 재정의 (USER_GUBUN_NM 한글 리터럴은 REPLACE 대상에 포함하지 않는다 - 그대로 보존)
|
||
SET @def_info = (
|
||
SELECT VIEW_DEFINITION FROM information_schema.views
|
||
WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = 'comvnuserinfo'
|
||
);
|
||
SET @def_info = REPLACE(@def_info, @qual, '');
|
||
|
||
SET @def_info = REPLACE(@def_info,
|
||
CONCAT('`', @n_gnr, '`.`MBER_NM` AS `USER_NM`'),
|
||
CONCAT('`', @n_gnr, '`.`MBER_NM_ENC` AS `USER_NM`'));
|
||
SET @def_info = REPLACE(@def_info,
|
||
CONCAT('`', @n_gnr, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT` from `', @n_gnr, '` union all'),
|
||
CONCAT('`', @n_gnr, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT`,`', @n_gnr, '`.`MBER_NM_HASH` AS `USER_NM_HASH` from `', @n_gnr, '` union all'));
|
||
SET @def_info = REPLACE(@def_info,
|
||
CONCAT('`', @n_grp, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT` from `', @n_grp, '` union all'),
|
||
CONCAT('`', @n_grp, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT`,NULL AS `USER_NM_HASH` from `', @n_grp, '` union all'));
|
||
SET @def_info = REPLACE(@def_info,
|
||
CONCAT('`', @n_ent, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT` from `', @n_ent, '` union all'),
|
||
CONCAT('`', @n_ent, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT`,NULL AS `USER_NM_HASH` from `', @n_ent, '` union all'));
|
||
SET @def_info = REPLACE(@def_info,
|
||
CONCAT('`', @n_emp, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT` from `', @n_emp, '`'),
|
||
CONCAT('`', @n_emp, '`.`ADMIN_UNLOCK_DT` AS `ADMIN_UNLOCK_DT`,NULL AS `USER_NM_HASH` from `', @n_emp, '`'));
|
||
|
||
SELECT
|
||
(LENGTH(@def_info) - LENGTH(REPLACE(@def_info, 'USER_NM_HASH', ''))) / LENGTH('USER_NM_HASH') AS hash_column_occurrences,
|
||
(@def_info LIKE '%MBER_NM_ENC%') AS gnr_uses_enc_column,
|
||
(@def_info NOT LIKE CONCAT('%`', @n_gnr, '`.`MBER_NM` AS `USER_NM`%')) AS old_plaintext_reference_removed;
|
||
|
||
-- ★ 하드 게이트 (comvnuserinfo)
|
||
IF (LENGTH(@def_info) - LENGTH(REPLACE(@def_info, 'USER_NM_HASH', ''))) / LENGTH('USER_NM_HASH') <> 4
|
||
OR @def_info NOT LIKE '%MBER_NM_ENC%'
|
||
OR @def_info LIKE CONCAT('%`', @n_gnr, '`.`MBER_NM` AS `USER_NM`%') THEN
|
||
SIGNAL SQLSTATE '45000'
|
||
SET MESSAGE_TEXT = 'PART4 ABORT: comvnuserinfo rewrite incomplete (expected 4 USER_NM_HASH + MBER_NM_ENC + no plaintext MBER_NM). View NOT replaced.';
|
||
END IF;
|
||
|
||
SET @sql_info = CONCAT('CREATE OR REPLACE VIEW `', @v_info, '` AS ', @def_info);
|
||
PREPARE stmt_info FROM @sql_info;
|
||
EXECUTE stmt_info;
|
||
DEALLOCATE PREPARE stmt_info;
|
||
|
||
-- 3. 반영 후 검증
|
||
-- ★ [2026-08-27] 이 검증 SELECT 들도 뉴 이름을 직접 참조하므로 동적 SQL 로 바꿈.
|
||
-- 운영(lower_case_table_names=0, 2026-08-27 실측)에서는 소문자 comvnusermaster 가
|
||
-- 존재하지 않아 1146(Table doesn't exist) 로 프로시저가 중단된다.
|
||
-- — 뉴 교체는 이미 성공한 뒤라, 성공한 컷오버가 실패처럼 보이게 된다.
|
||
-- (information_schema 비교는 콜레이션상 대소문자 무관이라 LOWER() 로 충분하다)
|
||
SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION
|
||
FROM information_schema.columns
|
||
WHERE TABLE_SCHEMA = DATABASE()
|
||
AND LOWER(TABLE_NAME) IN ('comvnusermaster', 'comvnuserinfo')
|
||
AND COLUMN_NAME IN ('USER_NM', 'USER_SE', 'USER_GUBUN', 'USER_NM_HASH')
|
||
ORDER BY TABLE_NAME, ORDINAL_POSITION;
|
||
|
||
SET @v_chk = CONCAT(
|
||
'SELECT USER_ID, USER_SE, LEFT(USER_NM, 20) AS user_nm_sample,',
|
||
' LEFT(USER_NM_HASH, 20) AS hash_sample FROM `', @v_master, '`',
|
||
' WHERE USER_SE = ''GNR'' LIMIT 1');
|
||
PREPARE stmt_chk FROM @v_chk; EXECUTE stmt_chk; DEALLOCATE PREPARE stmt_chk;
|
||
|
||
SET @v_chk = CONCAT(
|
||
'SELECT USER_ID, USER_SE, LEFT(USER_NM, 20) AS user_nm_sample, USER_NM_HASH',
|
||
' FROM `', @v_master, '` WHERE USER_SE = ''USR'' LIMIT 1');
|
||
PREPARE stmt_chk FROM @v_chk; EXECUTE stmt_chk; DEALLOCATE PREPARE stmt_chk;
|
||
|
||
SET @v_chk = CONCAT('SELECT USER_SE, COUNT(*) AS cnt FROM `', @v_master, '` GROUP BY USER_SE');
|
||
PREPARE stmt_chk FROM @v_chk; EXECUTE stmt_chk; DEALLOCATE PREPARE stmt_chk;
|
||
|
||
SET @v_chk = CONCAT(
|
||
'SELECT USER_GUBUN, LEFT(USER_NM, 20) AS user_nm_sample,',
|
||
' LEFT(USER_NM_HASH, 20) AS hash_sample FROM `', @v_info, '`',
|
||
' WHERE USER_GUBUN = ''GNR'' LIMIT 1');
|
||
PREPARE stmt_chk FROM @v_chk; EXECUTE stmt_chk; DEALLOCATE PREPARE stmt_chk;
|
||
|
||
SET @v_chk = CONCAT('SELECT USER_GUBUN, COUNT(*) AS cnt FROM `', @v_info, '` GROUP BY USER_GUBUN');
|
||
PREPARE stmt_chk FROM @v_chk; EXECUTE stmt_chk; DEALLOCATE PREPARE stmt_chk;
|
||
END IF;
|
||
END$$
|
||
DELIMITER ;
|
||
|
||
CALL PRC_PRIVACY_MBER_NM_VIEW_CUTOVER();
|
||
DROP PROCEDURE IF EXISTS PRC_PRIVACY_MBER_NM_VIEW_CUTOVER;
|
||
|
||
-- ── [롤백] ──
|
||
-- ★ 운영은 뷰 이름이 대문자(COMVNUSERMASTER)다. 소문자로 CREATE OR REPLACE 하면
|
||
-- lower_case_table_names=0 인 환경에서 **원본을 되돌리는 게 아니라 소문자 뷰가 새로
|
||
-- 하나 더 생긴다**. 아래처럼 실제 저장된 이름을 조회해서 그 이름으로 재실행할 것.
|
||
--
|
||
-- -- ① 실제 저장된 뷰 이름 확인
|
||
-- SELECT TABLE_NAME FROM information_schema.views
|
||
-- WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) IN ('comvnusermaster','comvnuserinfo');
|
||
--
|
||
-- -- ② 백업된 원본 정의 조회
|
||
-- SELECT view_definition FROM privacy_view_def_backup
|
||
-- WHERE view_name = 'comvnusermaster' ORDER BY backup_at DESC LIMIT 1;
|
||
--
|
||
-- -- ③ ①의 실제 이름 + ②의 값으로 재실행 (comvnuserinfo 동일)
|
||
-- -- CREATE OR REPLACE VIEW `COMVNUSERMASTER` AS <②의 value>
|
||
|
||
-- ############################## END ##############################
|