diff --git a/src/main/java/egovframework/com/cmm/privacy/auditlog/AbnormalAccessController.java b/src/main/java/egovframework/com/cmm/privacy/auditlog/AbnormalAccessController.java deleted file mode 100644 index 0389dd5..0000000 --- a/src/main/java/egovframework/com/cmm/privacy/auditlog/AbnormalAccessController.java +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Class Name : AbnormalAccessController.java - * Description : 비정상 접근 탐지 알림함 화면 — 개인정보보호 기능개선 사업(SFR-004 / SER-002 / #49) - * - * Modification Information - * - * 수정일 수정자 수정내용 - * ------- -------- --------------------------- - * 2026.08.27 개인정보보호 최초 생성 — 탐지 5유형(#35)의 화면 출력 - * - * @since 2026. 08. 27 - * @version 1.0 - */ -package egovframework.com.cmm.privacy.auditlog; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.List; - -import javax.annotation.Resource; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; - -import egovframework.com.cmm.LoginVO; -import egovframework.com.cmm.util.EgovUserDetailsHelper; - -/** - * 비정상 접근 탐지 알림함 (과업내용 다-① "비정상 접근 탐지 및 기록", SER-002). - * - *

{@link AbnormalAccessDetectionService}(#35)의 5유형 탐지 결과를 기간 단위로 보여준다. - * 화면: {@code /WEB-INF/jsp/site/mnt/sec/abnormal/list.jsp}

- * - *

왜 저장 테이블이 아니라 즉시 집계인가: 탐지 임계치는 정책값이라 언제든 바뀐다 - * ({@link AbnormalAccessPolicy}). 탐지 결과를 별도 테이블에 적재해 두면 임계치를 조정한 뒤에도 - * 과거 기준으로 굳은 행이 남아 "지금 기준으로 몇 건인가"에 답할 수 없다. 원천인 접속기록은 - * 730일 보존되므로, 조회 시점 기준으로 다시 세는 편이 정확하다. - * 월 단위 스냅샷이 필요한 경우는 정기점검 리포트(#43)가 {@code ABNORMAL_SUMMARY} 로 남긴다.

- * - *

조회 부하 주의: 5유형 각각이 접속기록 전체를 기간으로 훑는 집계라 기간이 넓으면 - * 무거워진다. 그래서 기본 조회기간을 최근 30일로 두고, 최대 {@link #MAX_RANGE_DAYS}일로 제한한다.

- * - *

접근통제: 인터셉터 ①인증판정이 비활성이라 화면 단에서 다시 판정한다 - * ({@link PersonalDataAccessLogController} 와 동일 방식). 롤 판정은 {@code urlRoleMapping} - * 의 {@code /mnt/sec/*} = ROLE_ADMIN 이 담당한다.

- */ -@Controller -public class AbnormalAccessController { - - private static final String LOGIN_REDIRECT = "redirect:/uat/uia/egovLoginUsr.do"; - - /** 기본 조회기간(일) — 최근 30일. */ - private static final int DEFAULT_RANGE_DAYS = 30; - - /** 최대 조회기간(일). 5유형 집계가 접속기록 전체를 훑으므로 상한을 둔다. */ - private static final int MAX_RANGE_DAYS = 366; - - private static final String DATE_PATTERN = "yyyy-MM-dd"; - - @Resource(name = "abnormalAccessDetectionService") - private AbnormalAccessDetectionService abnormalAccessDetectionService; - - /** - * 비정상 접근 탐지 결과 화면. - * - * @param fromDt 시작일(yyyy-MM-dd). 미지정 시 오늘 - 30일 - * @param toDt 종료일(yyyy-MM-dd). 미지정 시 오늘 - */ - @RequestMapping(value = "/mnt/sec/abnormal/listView.do") - public String listView(@RequestParam(value = "fromDt", required = false) String fromDt, - @RequestParam(value = "toDt", required = false) String toDt, - ModelMap model) throws Exception { - if (handlerUser() == null) { - return LOGIN_REDIRECT; - } - - SimpleDateFormat dateFmt = new SimpleDateFormat(DATE_PATTERN); - Calendar cal = Calendar.getInstance(); - - String toDate = isBlank(toDt) ? dateFmt.format(cal.getTime()) : toDt.trim(); - String fromDate; - if (isBlank(fromDt)) { - cal.add(Calendar.DAY_OF_MONTH, -DEFAULT_RANGE_DAYS); - fromDate = dateFmt.format(cal.getTime()); - } else { - fromDate = fromDt.trim(); - } - - String rangeMsg = null; - long days = diffDays(dateFmt, fromDate, toDate); - if (days < 0) { - // 시작 > 종료 — 조회하지 않고 되돌린다(뒤집힌 조건으로 "이상징후 0건"이 나오면 오독한다) - rangeMsg = "시작일이 종료일보다 뒤입니다. 기간을 다시 지정하세요."; - } else if (days > MAX_RANGE_DAYS) { - rangeMsg = "조회기간은 최대 " + MAX_RANGE_DAYS + "일까지입니다. 기간을 좁혀 주세요."; - } - - if (rangeMsg == null) { - // 종료일 당일(23:59:59)까지 포함 — 접속기록 조회화면과 같은 규칙 - List results = - abnormalAccessDetectionService.detect(fromDate + " 00:00:00", toDate + " 23:59:59"); - model.addAttribute("resultList", results); - model.addAttribute("summary", - abnormalAccessDetectionService.summarize(fromDate + " 00:00:00", toDate + " 23:59:59")); - } - - model.addAttribute("fromDt", fromDate); - model.addAttribute("toDt", toDate); - model.addAttribute("resultMsg", rangeMsg); - return "site/mnt/sec/abnormal/list"; - } - - /** 두 날짜(yyyy-MM-dd) 간 일수 차. 파싱 실패 시 0(=검증 통과)로 두고 조회를 진행한다. */ - private long diffDays(SimpleDateFormat fmt, String from, String to) { - try { - Date f = fmt.parse(from); - Date t = fmt.parse(to); - return (t.getTime() - f.getTime()) / (24L * 60L * 60L * 1000L); - } catch (Exception e) { - return 0L; - } - } - - private boolean isBlank(String v) { - return v == null || v.trim().length() == 0; - } - - /** - * 인증된 업무사용자(USR)만 통과. 그 외에는 null. - * (일반회원 세션으로 관리자 화면에 들어오는 경로를 여기서 끊는다) - */ - private LoginVO handlerUser() { - if (!Boolean.TRUE.equals(EgovUserDetailsHelper.isAuthenticated())) { - return null; - } - Object o = EgovUserDetailsHelper.getAuthenticatedUser(); - if (!(o instanceof LoginVO)) { - return null; - } - LoginVO loginVO = (LoginVO) o; - return "USR".equals(loginVO.getUserSe()) ? loginVO : null; - } -} diff --git a/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java b/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java index 81d445a..3300b85 100644 --- a/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java +++ b/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java @@ -97,26 +97,18 @@ public class PersonalDataAccessLogController { @RequestMapping(value = "/mnt/sec/accessLog/listView.do") public String listView(@ModelAttribute("personalDataAccessLogSearchVO") PersonalDataAccessLogSearchVO searchVO, ModelMap model) throws Exception { - return renderList(searchVO, model, false); + return renderList(searchVO, model); } /** - * 개인정보 처리기록 조회 화면 — 제안서 시안 좌측 (2) 메뉴. + * 목록 렌더링. * - *

(1)(접속기록 조회)이 접속·조회를 포함한 전체 기록이라면, 이 화면은 조회(S)를 빼고 - * 등록·수정·삭제만 남긴다. 정보주체 소명이나 감사 대응에서 "누가 이 사람의 개인정보를 - * 바꿨는가"를 먼저 보게 되는 쪽이라 별도 진입점을 둔다. 화면(JSP)과 검색조건은 (1)과 공유하고 - * {@code excludeSelect} 조건 하나만 다르다 — 같은 표를 두 벌 관리하지 않기 위해서다.

+ *

화면은 이 하나다. 제안요청서 과업내용 "다"가 요구하는 화면은 + * "관리자용 로그 조회 화면" 한 개이며, 제안요약서 시안도 이 한 장만 그려져 있다. + * 처리기록(등록·수정·삭제) 조회는 별도 화면이 아니라 처리구분 필터로, + * 비정상 접근 탐지는 목록의 감사 칸으로 이 화면 안에서 제공한다.

*/ - @RequestMapping(value = "/mnt/sec/processLog/listView.do") - public String processListView(@ModelAttribute("personalDataAccessLogSearchVO") PersonalDataAccessLogSearchVO searchVO, - ModelMap model) throws Exception { - searchVO.setExcludeSelect(true); - return renderList(searchVO, model, true); - } - - /** (1)·(2) 공통 렌더링. {@code processOnly} 는 화면 제목·안내 문구 분기에만 쓴다. */ - private String renderList(PersonalDataAccessLogSearchVO searchVO, ModelMap model, boolean processOnly) + private String renderList(PersonalDataAccessLogSearchVO searchVO, ModelMap model) throws Exception { if (handlerUser() == null) { return LOGIN_REDIRECT; @@ -138,7 +130,6 @@ public class PersonalDataAccessLogController { model.addAttribute("dmlSummary", selectDmlSummary(searchVO)); // 내 다운로드 작업 목록(비동기) — 상태·진행률 확인 및 내려받기 model.addAttribute("dlJobList", selectMyDownloadJobs()); - model.addAttribute("processOnly", Boolean.valueOf(processOnly)); model.addAttribute("retentionDays", Integer.valueOf(ACCESS_LOG_RETENTION_DAYS)); // 시안의 "감사" 칸 — 목록 각 행에 이상/점검 표시를 채우고 기간 전체 탐지 요약을 함께 싣는다 markAudit(list, searchVO, model); diff --git a/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogSearchVO.java b/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogSearchVO.java index 0d5c348..c9afc2f 100644 --- a/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogSearchVO.java +++ b/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogSearchVO.java @@ -58,17 +58,6 @@ public class PersonalDataAccessLogSearchVO implements Serializable { /** 전체 레코드 수(조회 후 세팅) */ private int totalRecordCount = 0; - /** - * 조회(S)를 제외하고 등록·수정·삭제만 보여줄지 여부. - * - *

제안서 시안의 좌측 ② "개인정보 처리기록" 메뉴가 이 조건을 켠 화면이다. - * ① "접속기록 조회"가 접속·조회를 포함한 전체 기록이라면, ②는 개인정보를 실제로 - * 바꾼 행위만 추린 화면이다 — 정보주체 소명·감사 대응에서 먼저 보게 되는 쪽이다.

- * - *

{@code dmlGubun}(단일 값 지정)과는 다른 조건이다. 둘 다 지정되면 SQL 에서 AND 로 걸려 - * 교집합이 되므로, 화면에서는 한쪽만 쓰게 한다.

- */ - private boolean excludeSelect = false; public String getConectId() { return conectId; } public void setConectId(String conectId) { this.conectId = conectId; } @@ -106,8 +95,6 @@ public class PersonalDataAccessLogSearchVO implements Serializable { public int getTotalRecordCount() { return totalRecordCount; } public void setTotalRecordCount(int totalRecordCount) { this.totalRecordCount = totalRecordCount; } - public boolean isExcludeSelect() { return excludeSelect; } - public void setExcludeSelect(boolean excludeSelect) { this.excludeSelect = excludeSelect; } // ===================================================================== // SQL 바인딩용 파생값 (2026-08-07) — setter 없음(읽기 전용). diff --git a/src/main/java/egovframework/com/cmm/privacy/batch/InspectionResultController.java b/src/main/java/egovframework/com/cmm/privacy/batch/InspectionResultController.java index a8edb8d..e4647a6 100644 --- a/src/main/java/egovframework/com/cmm/privacy/batch/InspectionResultController.java +++ b/src/main/java/egovframework/com/cmm/privacy/batch/InspectionResultController.java @@ -1,6 +1,5 @@ package egovframework.com.cmm.privacy.batch; -import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -13,20 +12,13 @@ import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import egovframework.com.cmm.LoginVO; -import egovframework.com.cmm.util.EgovUserDetailsHelper; /** * 정기점검 결과 입력·보관 컨트롤러 (#44, SFR-004/SER-002). * - *

취급자 영역(/mnt) 엔드포인트. 기존 {@code list.do}/{@code detail.do}/{@code saveResult.do} - * 는 jsonView 로 응답하는 백엔드 계약이고, 2026.08.27 추가된 {@code listView.do}/ - * {@code saveResultView.do} 가 관리자 화면(#48)이다 — 두 계약을 병존시켜 기존 호출부를 깨지 않는다. + *

취급자 영역(/mnt) 백엔드 엔드포인트. 응답은 jsonView(화면 JSP는 #48 대시보드 후속). * 점검자 식별자는 세션 loginVO 에서 취득한다.

* - *

화면이 필요한 이유: 월 1회 점검 자체는 배치(#43)가 집계하지만, "누가 언제 보고 무엇을 - * 조치했는가"는 사람이 입력해야 남는다. 그 입력 경로가 JSON 엔드포인트뿐이면 점검 사실을 - * 증빙으로 제출할 수 없다(SFR-004-7).

- * * @author arko-privacy-auditlog (Fork C - 7차) * @since 2026-07-21 */ @@ -35,8 +27,6 @@ public class InspectionResultController { protected static final String JSON = "jsonView"; - private static final String LOGIN_REDIRECT = "redirect:/uat/uia/egovLoginUsr.do"; - @Resource(name = "inspectionResultService") private InspectionResultService inspectionResultService; @@ -45,82 +35,6 @@ public class InspectionResultController { return (o instanceof LoginVO) ? (LoginVO) o : null; } - /** - * 정기점검 리포트 목록·상세 화면(서버 렌더링, #48). - * 뷰: /WEB-INF/jsp/site/mnt/sec/inspection/list.jsp - * - *

월 1행짜리 표라 페이징을 두지 않는다(연 12행). 연도 필터와 점검여부 필터만 제공한다. - * {@code rptId} 가 있으면 그 리포트 상세와 점검결과 입력 폼을 함께 편다 — - * 목록에서 상세로 화면을 옮기면 점검 중 다른 달과 대조하기가 불편하다.

- */ - @RequestMapping(value = "/mnt/sec/inspection/listView.do") - public String listView(@org.springframework.web.bind.annotation.RequestParam(value = "rptYmPrefix", required = false) String rptYmPrefix, - @org.springframework.web.bind.annotation.RequestParam(value = "inspectedYn", required = false) String inspectedYn, - @org.springframework.web.bind.annotation.RequestParam(value = "rptId", required = false) Long rptId, - @org.springframework.web.bind.annotation.RequestParam(value = "resultMsg", required = false) String resultMsg, - ModelMap model) throws Exception { - if (handlerUser() == null) { - return LOGIN_REDIRECT; - } - Map param = new HashMap(); - param.put("rptYmPrefix", rptYmPrefix); - param.put("inspectedYn", inspectedYn); - List list = inspectionResultService.selectReportList(param); - - model.addAttribute("resultList", list); - model.addAttribute("totalCount", list == null ? 0 : list.size()); - model.addAttribute("rptYmPrefix", rptYmPrefix); - model.addAttribute("inspectedYn", inspectedYn); - model.addAttribute("resultMsg", resultMsg); - if (rptId != null) { - model.addAttribute("report", inspectionResultService.selectReport(rptId.longValue())); - } - return "site/mnt/sec/inspection/list"; - } - - /** - * 점검결과 입력·보관(화면용, #48). 저장 후 같은 리포트를 펼친 목록으로 되돌린다. - * - *

배치가 적재한 집계값은 손대지 않고 점검자 입력 필드만 갱신한다 - * (감사 증빙이므로 집계값이 사후에 바뀌면 안 된다).

- */ - @RequestMapping(value = "/mnt/sec/inspection/saveResultView.do") - public String saveResultView(InspectionReportVO vo, HttpServletRequest request) throws Exception { - LoginVO user = handlerUser(); - if (user == null) { - return LOGIN_REDIRECT; - } - vo.setInspectorId(user.getId()); - String message; - try { - int cnt = inspectionResultService.saveInspectionResult(vo); - message = (cnt > 0) ? "점검결과를 저장했습니다." : "저장 대상 리포트가 없습니다."; - } catch (IllegalArgumentException e) { - // 점검결과 미입력 등 유효성 위반 — 화면에 사유를 그대로 보여준다 - message = e.getMessage(); - } - // Spring 3.0.5 에는 RedirectAttributes 가 없다(3.1 도입). 이 코드베이스의 관용대로 - // 결과 메시지를 URL 인코딩해 쿼리스트링으로 넘긴다(AccountMngController 와 동일 방식). - return "redirect:/mnt/sec/inspection/listView.do?rptId=" + vo.getRptId() - + "&resultMsg=" + URLEncoder.encode(message == null ? "" : message, "UTF-8"); - } - - /** - * 인증된 업무사용자(USR)만 통과. 그 외에는 null. - * (인터셉터 ①인증판정이 비활성이라 화면 단에서 다시 판정한다 — 다른 개인정보보호 화면과 동일) - */ - private LoginVO handlerUser() { - if (!Boolean.TRUE.equals(EgovUserDetailsHelper.isAuthenticated())) { - return null; - } - Object o = EgovUserDetailsHelper.getAuthenticatedUser(); - if (!(o instanceof LoginVO)) { - return null; - } - LoginVO loginVO = (LoginVO) o; - return "USR".equals(loginVO.getUserSe()) ? loginVO : null; - } - /** 정기점검 리포트 월별 목록 (검색: rptYmPrefix 연도, inspectedYn Y/N) */ @RequestMapping(value = "/mnt/sec/inspection/list.do") public String list(@org.springframework.web.bind.annotation.RequestParam(value = "rptYmPrefix", required = false) String rptYmPrefix, diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml index b9bf511..77bcec1 100644 --- a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml +++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml @@ -113,9 +113,6 @@ CONECT_IP LIKE #conectIpLike# ESCAPE '!' DML_GUBUN = #dmlGubun# PROCESS_RESULT = #processResult# - - DML_GUBUN <> 'S' CONECT_DT >= #fromDtParam# CONECT_DT <= #toDtParam# @@ -138,9 +135,6 @@ CONECT_IP LIKE #conectIpLike# ESCAPE '!' DML_GUBUN = #dmlGubun# PROCESS_RESULT = #processResult# - - DML_GUBUN <> 'S' CONECT_DT >= #fromDtParam# CONECT_DT <= #toDtParam# @@ -169,9 +163,6 @@ CONECT_IP LIKE #conectIpLike# ESCAPE '!' DML_GUBUN = #dmlGubun# PROCESS_RESULT = #processResult# - - DML_GUBUN <> 'S' CONECT_DT >= #fromDtParam# CONECT_DT <= #toDtParam# diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml index e42d23b..b7d7d7d 100644 --- a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml +++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml @@ -96,9 +96,6 @@ CONECT_IP LIKE #conectIpLike# ESCAPE '!' DML_GUBUN = #dmlGubun# PROCESS_RESULT = #processResult# - - DML_GUBUN <> 'S' CONECT_DT >= #fromDtParam# CONECT_DT <= #toDtParam# @@ -120,9 +117,6 @@ CONECT_IP LIKE #conectIpLike# ESCAPE '!' DML_GUBUN = #dmlGubun# PROCESS_RESULT = #processResult# - - DML_GUBUN <> 'S' CONECT_DT >= #fromDtParam# CONECT_DT <= #toDtParam# @@ -143,9 +137,6 @@ CONECT_IP LIKE #conectIpLike# ESCAPE '!' DML_GUBUN = #dmlGubun# PROCESS_RESULT = #processResult# - - DML_GUBUN <> 'S' CONECT_DT >= #fromDtParam# CONECT_DT <= #toDtParam# diff --git a/src/main/webapp/WEB-INF/jsp/site/mnt/left.jsp b/src/main/webapp/WEB-INF/jsp/site/mnt/left.jsp index 930d0ce..5f49b87 100644 --- a/src/main/webapp/WEB-INF/jsp/site/mnt/left.jsp +++ b/src/main/webapp/WEB-INF/jsp/site/mnt/left.jsp @@ -94,10 +94,10 @@ -
  • class="on">${menuUpperVo2.menuNm} +
  • class="on">${menuUpperVo2.menuNm} -
      style="display: block;"> +
        style="display: block;"> @@ -122,6 +122,11 @@
      • class="on">${menuUpperVo4.menuNm}
      • + <%-- [개인정보/RFP 과업내용 다] 접속기록 조회 및 감사 — 이 영역이 요구하는 화면은 이 하나다. + 처리기록은 처리구분 필터, 이상행위는 목록의 감사 칸, 파기이력은 화면 내 버튼으로 제공한다. --%> + +
      • class="on">${menuUpperVo4.menuNm}
      • +
        <%-- [2026-08-11] 9100045 업무유형 매핑 관리 / 9100046 로그인·로그아웃 접속기록 분기 삭제(화면·메뉴 폐기) --%> <%-- [개인정보/SER-001 요건4·5] 접근통제 정책 설정(계정잠금·자동로그아웃·세션재발급) --%> @@ -139,40 +144,6 @@ - <%-- ============================================================ - [개인정보/제안서 Ⅲ-4 시안] 접속기록 · 감사 (9100052) - ① 접속기록 조회(9100044) ② 개인정보 처리기록(9100053) - ③ 이상행위 감사(9100049) ④ 정기 점검 리포트(9100048) - 제안서 시안이 이 넷을 한 묶음으로 그리고 있어 권한관리 하위에서 이설했다. - 접속기록 파기이력은 시안에 메뉴가 없어 정기 점검 리포트 화면 안에서 연다. - ============================================================ --%> - -
      • class="on">${menuUpperVo2.menuNm} - - - - - -
      • -
        -
      • class="on">${menuUpperVo2.menuNm} diff --git a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/abnormal/list.jsp b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/abnormal/list.jsp deleted file mode 100644 index c724db4..0000000 --- a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/abnormal/list.jsp +++ /dev/null @@ -1,221 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%-- Head --%> -<%--// Head --%> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> - -<%-- - 비정상 접근 탐지 알림함 (개인정보보호 과업내용 다-① / SER-002 / #49) - · AbnormalAccessDetectionService(#35)의 5유형을 기간 단위로 즉시 집계해 보여준다 - · 탐지 결과를 별도 테이블에 적재하지 않는 이유는 컨트롤러 Javadoc 참조(임계치 변경 시 과거 기준이 굳는 문제) - · 각 항목은 접속기록 조회화면(#47)으로 조건을 넘겨 바로 원본 기록을 확인할 수 있게 연결한다 ---%> - - - - - - - - - - - - -
        -
        - -
        - -
        - -
        - -
        - -
        - · 개인정보 접속기록(730일 보존)을 조회 시점의 정책 임계치로 다시 집계한 결과입니다. 탐지 결과를 따로 저장하지 않으므로 임계치를 조정하면 과거 기간도 새 기준으로 다시 계산됩니다.
        - · 탐지되었다는 것이 곧 위반은 아닙니다. 업무상 정당한 대량조회·야간작업이 있을 수 있으므로, 각 항목의 접속기록 보기로 원본 기록을 확인한 뒤 판단하세요.
        - · 월 단위 점검 기록으로 남겨야 하는 경우에는 정기점검 리포트 화면에서 점검결과·조치사항을 입력하세요(이 화면은 조회 전용입니다).
        - · 5유형 각각이 기간 전체를 훑는 집계라 기간이 넓으면 조회가 느려집니다(최대 366일). -
        - - -
        - ~ 탐지 요약  :  -
        -
        - - <%-- 유형별 요약 카드 5장 --%> -
        - -
        -
        -
        - - - - 건 - 명 - - -
        -
        -
        -
        -
        -
        - - <%-- 유형별 상세 --%> - -
        -

        - - - - 처리결과가 실패·차단(F)으로 남은 기록입니다 — 권한 없는 화면 접근 시도가 여기에 잡힙니다. - 단위기간 조회(S) 건수가 임계를 넘은 취급자입니다. - 업무시간 밖(기본 09시 이전·18시 이후)에 처리된 기록입니다. - 같은 계정이 서로 다른 IP에서 사용된 경우입니다 — 계정 공유·탈취 의심 신호입니다. - 같은 취급자에게서 실패(F) 기록이 반복된 경우입니다. - - -

        - - - <%-- 대상자형(대량조회·계정공유·인증실패반복) : 누가 걸렸는지 목록으로 --%> - - - - - - - - - - - - - - - - - - - - - - -
        의심 대상 목록
        NO.접속자ID - - 사용 IP 수 - 실패 횟수 - 조회 건수 - - 원본 확인
        - - - - - - <%-- 접속기록 조회화면(#47)으로 조건을 넘겨 원본을 바로 확인한다. - 인증실패반복은 처리결과 F, 대량조회는 처리구분 S 로 좁혀 준다. --%> - &processResult=F&dmlGubun=S">접속기록 보기 -
        -
        - - <%-- 건수형(비인가접근·비정상시간대) : 대상자가 아니라 기간 전체 건수라 목록이 없다 --%> - - - - -
        - 기간 내 건 — - 접속기록에서 해당 기간 기록 보기 - (시간대 조건은 접속기록 화면의 접속일시로 확인하세요) -
        -
        - -
        해당 기간에 탐지된 항목이 없습니다.
        -
        -
        -
        -
        - - - - - - - -
        비정상 접근 탐지 결과
        -
        - - - - - - - - - diff --git a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/accessLog/list.jsp b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/accessLog/list.jsp index 029b9fa..1d7484e 100644 --- a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/accessLog/list.jsp +++ b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/accessLog/list.jsp @@ -14,17 +14,13 @@ · 이 테이블은 UPDATE/DELETE statement 미정의(위·변조 방지) — 화면에서도 조회/다운로드만 제공 --%> -<%-- 제안서 시안 좌측 (1)접속기록 조회 / (2)개인정보 처리기록 — 같은 화면을 조건만 바꿔 쓴다. - 목록 재조회·페이징이 자기 화면으로 돌아오도록 진입 URL 을 여기서 정한다. --%> - - - - - - - - - - - - -
        -
        - -
        - -
        - -
        - -
        - -
        - · 매월 1일 배치가 전월분 접속기록을 집계해 리포트 1건을 생성합니다(개인정보의 안전성 확보조치 기준 제8조 — 월 1회 이상 점검).
        - · 집계값(총 처리건수·실패건수·무결성 검증결과·이상징후·다운로드 건수)은 배치가 적재한 값이며 화면에서 수정할 수 없습니다 — 사후 변경이 가능하면 점검 증빙이 되지 못합니다.
        - · 점검자가 입력하는 항목은 점검결과·조치사항이며, 저장 시 점검자 계정과 점검일시가 자동 기록됩니다.
        - · 무결성 위반(VIOLATION)은 접속기록 해시체인이 끊긴 상태로, 위·변조 가능성을 뜻합니다. 최초 불일치 기록번호를 확인해 즉시 조치사항에 남기세요. -
        - -
        - 총 건 - <%-- 접속기록 파기이력 진입점. - 제안서 시안의 좌측메뉴에는 파기이력이 없고, 파기는 "보관·점검"의 일부로 그려져 있다. - 그래서 별도 메뉴 대신 점검 화면에서 연다 — 점검할 때 "규정대로 파기됐는가"를 - 같은 자리에서 확인하는 편이 실제 감사 흐름과도 맞는다. --%> - ?page=9100048';" /> -
        - - - - - - - - - - - - - - - - - - - - - - class="on"> - - - - - - - - - - - - - - - - -
        정기점검 리포트 목록
        대상연월집계기간총 처리실패/차단무결성이상징후 요약점검여부점검자/일시
        '); return false;"> ~ - - 정상 - 위반 - - - - 점검완료 - 미점검 - - - - -
        -
        - - -
        -
        - - <%-- 상세 + 점검결과 입력 --%> - -
        -

        정기점검 리포트 상세

        -
        -
        -
        집계기간
        -
        ~
        -
        총 처리건수
        -
        건 (실패/차단 건)
        -
        다운로드
        -
        건
        -
        무결성 검증
        -
        - - - 정상 검증대상 건 — 해시체인 연결 이상 없음 - - - 위반 검증대상 건 — - 최초 불일치 기록번호 - - -
        -
        이상징후
        -
        -
        취급자별 현황
        -
        -
        리포트 생성
        -
        -
        - -
        - - - - - - - - - - - - - - - - - - -
        점검결과 입력
        점검결과 * - -
        조치사항 - -
        점검자 / 점검일시 - - - / - (저장 시 현재 로그인 계정·시각으로 갱신됩니다) - - 저장 시 현재 로그인 계정·시각이 자동 기록됩니다. - -
        -
        - -
        -
        -
        -
        -
        - - - - - - - - - diff --git a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/purgeHistory/list.jsp b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/purgeHistory/list.jsp index 6b690e0..ee56588 100644 --- a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/purgeHistory/list.jsp +++ b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/purgeHistory/list.jsp @@ -78,10 +78,10 @@ function fn_list(pageIndex) {
        총 건 - <%-- 이 화면은 좌측메뉴에 없다(제안서 시안에 파기이력 메뉴가 없음). - 정기 점검 리포트에서 들어오므로 돌아갈 길을 같이 둔다. --%> - ?page=9100048';" /> + <%-- 이 화면은 좌측메뉴에 없다 — RFP 가 요구하는 화면은 "접속기록 조회 및 감사" 하나다. + 그 화면의 "파기이력" 버튼으로 들어오므로 돌아갈 길을 같이 둔다. --%> + ?page=9100044';" />
        diff --git a/src/script/mysql/dml/privacy.menu_audit_single_screen_mysql.sql b/src/script/mysql/dml/privacy.menu_audit_single_screen_mysql.sql new file mode 100644 index 0000000..d32f384 --- /dev/null +++ b/src/script/mysql/dml/privacy.menu_audit_single_screen_mysql.sql @@ -0,0 +1,63 @@ +-- ===================================================================== +-- 관리자 메뉴 정리 : 접속·처리기록 관리를 화면 1개로 통합 +-- 문화품앗이 개인정보보호 기능개선 사업 (RFP 과업내용 다 / SFR-004) +-- 작성일: 2026-08-27 DB: MySQL 8 / MariaDB (스키마 csv) +-- --------------------------------------------------------------------- +-- [근거] 제안요청서 과업내용 "다. 개인정보취급자 접속 및 처리기록 관리 기능 개발" 이 +-- 요구하는 화면은 하나다 — +-- "ㅇ 로그 조회 및 관리 기능 구현 +-- - 관리자용 로그 조회 화면 개발 +-- - 조건별 검색 및 다운로드 기능 제공 +-- - 로그 다운로드 이력, 무결성 확보 및 관리 기능 적용" +-- 제안요약서 slide 14~15 의 시안도 이 한 화면("접속기록 조회 및 감사")만 그려져 있다. +-- +-- [따라서] 2026-08-27 에 만들었던 "접속기록 · 감사" 그룹(9100052)과 하위 3개 메뉴 +-- (개인정보 처리기록 9100053 / 이상행위 감사 9100049 / 정기 점검 리포트 9100048)를 +-- 메뉴에서 내리고, 접속기록 화면 하나로 되돌린다. +-- +-- ★ 기능은 지우지 않는다 ★ +-- RFP 는 이 셋을 '기능'으로는 명시하고 있다 — +-- · "비정상 접근 탐지 및 기록 기능 구현" → 목록의 '감사' 칸(이상/점검)으로 상시 표시 +-- · "처리기록 검색·조회 기능 제공" → 처리구분(조회/등록/수정/삭제) 필터로 제공 +-- · SFR-004 "접속기록 정기점검을 지원" → 화면 내 버튼으로 진입 +-- 화면(컨트롤러·JSP)은 그대로 살아 있고 진입 동선만 접속기록 화면 안으로 옮긴다. +-- 메뉴에서 내렸다고 기능이 없어진 것이 아니다 — 검수 시 이 주석이 근거다. +-- +-- [남는 메뉴] 권한관리(9100030) 하위 ORDR 10 : "접속기록 조회 및 감사" (9100044) +-- +-- [재실행 안전] UPDATE 는 같은 값으로 수렴, DELETE 는 대상 한정 → 반복 실행 무해. +-- [한글 리터럴 주의] SOURCE 로 실행하고 적용 후 HEX(MENU_NM) 확인. +-- ★★ left.jsp 동반 배포 필수 — 9100052 그룹 블록 제거 + 권한관리 블록에 9100044 분기 복원 ★★ +-- ===================================================================== + +-- ── [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] 하위 3개 메뉴 내리기 (화면·컨트롤러·프로그램 등록은 유지) ── +-- 9100048 정기 점검 리포트 / 9100049 이상행위 감사 / 9100053 개인정보 처리기록 +DELETE FROM COMTNMENUCREATDTLS WHERE MENU_NO IN (9100048, 9100049, 9100053); +DELETE FROM COMTNMENUINFO WHERE MENU_NO IN (9100048, 9100049, 9100053); + +-- ── [3] "접속기록 · 감사" 그룹(9100052) 제거 ── +DELETE FROM COMTNMENUCREATDTLS WHERE MENU_NO = 9100052; +DELETE FROM COMTNMENUINFO WHERE MENU_NO = 9100052; + +-- ── [검증] 권한관리 하위에 9100044 하나만 남고 9100048/49/52/53 이 없어야 한다 ── +-- SELECT MENU_NO, UPPER_MENU_NO, MENU_ORDR, MENU_NM, HEX(MENU_NM) +-- FROM COMTNMENUINFO WHERE MENU_NO IN (9100044,9100048,9100049,9100052,9100053) +-- ORDER BY MENU_ORDR; +-- → 9100044 / 9100030 / 10 / 접속기록 조회 및 감사 (1행만) + +-- ── [롤백] 그룹 구조로 되돌리기 ── +-- privacy.menu_privacy_screens4_insert_mysql.sql 재실행(메뉴 재생성) +-- → privacy.menu_audit_group_regroup_mysql.sql 재실행(그룹 재편) diff --git a/src/script/mysql/privacy.install_mysql.sql b/src/script/mysql/privacy.install_mysql.sql index 77ceaf6..0bb7128 100644 --- a/src/script/mysql/privacy.install_mysql.sql +++ b/src/script/mysql/privacy.install_mysql.sql @@ -5,10 +5,10 @@ -- → [PART 2] DML(기준/시드 데이터) -- → [PART 3] 관리자 메뉴 등록 DML(좌측메뉴 노출, COMTNPROGRMLIST/ -- COMTNMENUINFO/COMTNMENUCREATDTLS, --- 9100035~9100039 + 9100044 + 9100047 + 9100048~9100051 --- + 9100052(접속기록·감사 그룹) + 9100053(개인정보 처리기록)) --- ※ [8]에서 9100044/48/49 를 9100052 하위로 이설하고 9100050(파기이력) --- 메뉴는 내린다 — 제안서 Ⅲ-4 화면 시안 정합 +-- 9100035~9100039 + 9100044 + 9100047 + 9100051) +-- ※ 접속·처리기록 영역의 화면은 9100044 "접속기록 조회 및 감사" 하나다 +-- (RFP 과업내용 다 = "관리자용 로그 조회 화면" 단수, 시안도 한 장). +-- 처리기록·이상행위·파기이력은 그 화면 안에서 제공한다. -- ※ 9100045(업무유형 매핑 관리)·9100046(로그인 접속기록)은 2026-08-11 폐기 -- — 폐기된 번호는 재사용하지 않는다(과거 권한매핑 잔재와 섞인다) -- → [PART 4] 뷰 GNR 컷오버(comvnusermaster/comvnuserinfo, 회원 이름 암호화 @@ -95,7 +95,7 @@ CREATE TABLE IF NOT EXISTS ACCOUNT_MNG_POLICY ( UPD_ID VARCHAR(20) NULL COMMENT '최종 수정자', UPD_DT DATETIME NULL COMMENT '최종 수정일시', PRIMARY KEY (POLICY_ID) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='계정 관리 정책(개인정보보호 SFR-002, 단일행)'; -- 기본 정책 시드(없을 때만). 기준 365일·알림 D-30/7/0·보존 365일. @@ -127,7 +127,7 @@ CREATE TABLE IF NOT EXISTS ACCOUNT_UNLOCK_HISTORY ( 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='계정 잠금 해제 이력(개인정보보호 SER-001 #24)'; @@ -180,7 +180,7 @@ CREATE TABLE IF NOT EXISTS AUTHOR_CHANGE_HISTORY ( 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='권한 변경 이력(개인정보보호 SFR-001, 3년 이상 보관)'; -- ───────────────────────────────────────────────────────────── @@ -217,7 +217,7 @@ CREATE TABLE IF NOT EXISTS AUTHOR_CHANGE_REQUEST ( 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='권한 변경 신청·승인 워크플로(개인정보보호 SFR-001 #3)'; CREATE TABLE IF NOT EXISTS AUTHOR_HIST_DOWNLOAD_HISTORY ( @@ -231,7 +231,7 @@ CREATE TABLE IF NOT EXISTS AUTHOR_HIST_DOWNLOAD_HISTORY ( 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='권한이력 다운로드 증빙 이력(개인정보보호 SFR-001 #6)'; -- ───────────────────────────────────────────────────────────── @@ -280,7 +280,7 @@ CREATE TABLE IF NOT EXISTS PDAL_DOWNLOAD_HISTORY ( PRIMARY KEY (DL_ID), KEY IX_PDDH_DT (DOWNLOAD_DT), KEY IX_PDDH_USER (DOWNLOADER_ID, DOWNLOAD_DT) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='개인정보 접속기록 다운로드 이력(#42, SER-002)'; -- [Oracle] 시퀀스 필요: @@ -324,7 +324,7 @@ CREATE TABLE IF NOT EXISTS PDAL_INSPECTION_REPORT ( PRIMARY KEY (RPT_ID), UNIQUE KEY UX_PIR_YM (RPT_YM), KEY IX_PIR_REGDT (REG_DT) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='개인정보 접속기록 정기점검 리포트(#43, 월 1회)'; -- ───────────────────────────────────────────────────────────── @@ -467,7 +467,7 @@ CREATE TABLE IF NOT EXISTS OTP_DEVICE ( 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='OTP 2단계 인증 비밀키 영구 저장(개인정보보호 SFR-003 No.18)'; -- ───────────────────────────────────────────────────────────── @@ -581,7 +581,7 @@ CREATE TABLE IF NOT EXISTS ACCOUNT_DELETE_HISTORY ( PRIMARY KEY (DEL_ID), KEY IX_ADH_MBER (MBER_ID), KEY IX_ADH_DELDT (DEL_DT) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='장기 미사용 계정 개인정보 파기 이력(개인정보보호 SFR-002 #12)'; @@ -774,7 +774,7 @@ CREATE TABLE IF NOT EXISTS CONECT_IP_LOCATION ( UPDT_DT DATETIME NULL COMMENT '최종 변경일시', PRIMARY KEY (LOC_ID), UNIQUE KEY UK_CIL_CIDR (IP_CIDR) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='접속위치 매핑(IP 대역 → 위치명) — 로그인·로그아웃 접속기록 62'; @@ -819,7 +819,7 @@ CREATE TABLE IF NOT EXISTS AUTHOR_HIST_DOWNLOAD_JOB ( PRIMARY KEY (JOB_ID), KEY IX_AHDJ_REQUESTER (REQUESTER_ID, REG_DT), KEY IX_AHDJ_STATUS (STATUS, REG_DT) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='권한변경 이력 CSV 비동기 다운로드 작업'; @@ -847,7 +847,7 @@ CREATE TABLE IF NOT EXISTS ACCOUNT_ACTION_HISTORY ( 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 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='장기 미사용 계정 조치(잠금/말소) 이력(개인정보보호 SFR-002 #11)'; @@ -870,7 +870,7 @@ CREATE TABLE IF NOT EXISTS LOGIN_LOCK_EXEMPT ( REG_ID VARCHAR(20) NULL COMMENT '등록한 관리자 계정ID', REG_DT DATETIME NULL COMMENT '등록일시', PRIMARY KEY (EXEMPT_ID) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='로그인 실패 계정잠금 예외 계정(개인정보보호 SER-001 24, 최고관리자 잠김 방지)'; @@ -896,7 +896,7 @@ CREATE TABLE IF NOT EXISTS ACCESS_CONTROL_POLICY ( UPD_ID VARCHAR(20) NULL COMMENT '최종 수정자', UPD_DT DATETIME NULL COMMENT '최종 수정일시', PRIMARY KEY (POLICY_ID) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='접근통제 정책(개인정보보호 SER-001 요건4-5, 단일행)'; INSERT INTO ACCESS_CONTROL_POLICY @@ -956,7 +956,7 @@ CREATE TABLE IF NOT EXISTS PDAL_DOWNLOAD_JOB ( PRIMARY KEY (JOB_ID), KEY IX_PDJ_REQUESTER (REQUESTER_ID, REG_DT), KEY IX_PDJ_STATUS (STATUS, REG_DT) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='개인정보 접속기록 CSV 비동기 다운로드 작업'; @@ -1097,7 +1097,7 @@ CREATE TABLE IF NOT EXISTS MIG_CRYPTO_PROGRESS ( ON UPDATE CURRENT_TIMESTAMP COMMENT '갱신 일시', RM VARCHAR(500) NULL COMMENT '비고(마지막 오류 메시지 등)', PRIMARY KEY (JOB_ID) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='개인정보 암호화 이관 배치 진행상태'; @@ -1172,6 +1172,71 @@ 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) @@ -1195,7 +1260,7 @@ CREATE TABLE IF NOT EXISTS MBER_CRYPTO_ACCESS_HISTORY KEY IDX_MCAH_01 (ACCESS_DT), KEY IDX_MCAH_02 (HANDLR_ID), KEY IDX_MCAH_03 (TRGET_MBER_ID) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='암호화 회원정보 관리자 접근 이력(사유·재인증, 추가제안 Action4)'; @@ -1252,9 +1317,9 @@ WHERE NOT EXISTS (SELECT 1 FROM CONECT_IP_LOCATION x WHERE x.IP_CIDR = seed.IP_C -- [1]~[5] 9100035~9100039 권한 신청·승인 / 권한 변경 이력 / 미사용계정 현황 / -- 계정 관리 정책 / 인증 실패 이력 -- [6] 9100047(ORDR 13) 접근통제 정책 --- [7] 9100048~9100051(ORDR 14~17) 2026-08-27 추가 --- 정기점검 리포트 / 비정상접근 탐지 / 접속기록 파기이력 / 회원정보 암호화 관리 --- ※ 접속기록 조회(9100044, ORDR 10)는 위 [1]~[5] 블록에 함께 들어 있다. +-- [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 @@ -1269,7 +1334,7 @@ WHERE NOT EXISTS (SELECT 1 FROM CONECT_IP_LOCATION x WHERE x.IP_CIDR = seed.IP_C -- 권한관리(9100030) 블록에 등록한 메뉴번호마다 분기 + -- 시스템관리/권한관리 상위 하이라이트 조건(param.page eq '91000xx')이 -- 반드시 함께 배포돼야 렌더된다. (arko_renual 소스에는 9100035~9100039·9100044· --- 9100047·9100048~9100051 전부 이미 반영 완료 — 운영 배포 시 WAR/배포본에 이 JSP가 +-- 9100047·9100051 전부 이미 반영 완료 — 운영 배포 시 WAR/배포본에 이 JSP가 -- 포함됐는지만 확인하면 됨) -- -- [재실행 안전] 모든 INSERT NOT EXISTS 가드 → 반복 실행 무해. @@ -1404,40 +1469,42 @@ SELECT 9100047, g.ac -- ('AuthorRequestList','AuthorHistoryList','DormantStatusList','AccountPolicySet'); --- ── [7] 개인정보보호 화면 4종 메뉴(9100048~9100051) — 2026-08-27 추가 ── --- 9100048 / ORDR 14 정기점검 리포트 /mnt/sec/inspection/listView.do (SFR-004-7) --- 9100049 / ORDR 15 비정상접근 탐지 /mnt/sec/abnormal/listView.do (과업내용 다-①) --- 9100050 / ORDR 16 접속기록 파기이력 /mnt/sec/purgeHistory/listView.do (SER-002-2) --- 9100051 / ORDR 17 회원정보 암호화관리 /mnt/sec/mberCrypto/listView.do (추가제안 Action4) --- ★ left.jsp 9100048~9100051 분기 4개 + 상위 하이라이트 조건 2곳 동반 배포 필수 --- ※ 9100045·9100046 은 2026-08-11 화면 폐기와 함께 삭제된 번호라 재사용하지 않는다. -INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL) -SELECT * FROM ( - SELECT 'InspectionReportList' AS PROGRM_FILE_NM, '/mnt/sec/inspection/' AS PROGRM_STRE_PATH, - '정기점검 리포트' AS PROGRM_KOREAN_NM, - '접속기록 정기점검 리포트 조회·점검결과 입력(SFR-004-7)' AS PROGRM_DC, - '/mnt/sec/inspection/listView.do?page=9100048' AS URL -) seed -WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM); +-- ── [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 는 대상 한정. -INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL) -SELECT * FROM ( - SELECT 'AbnormalAccessList' AS PROGRM_FILE_NM, '/mnt/sec/abnormal/' AS PROGRM_STRE_PATH, - '비정상접근 탐지' AS PROGRM_KOREAN_NM, - '비정상 접근 탐지 5유형 조회(과업내용 다-①)' AS PROGRM_DC, - '/mnt/sec/abnormal/listView.do?page=9100049' AS URL -) seed -WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM); +-- (1) 접속기록 화면 명칭 정리 +UPDATE COMTNMENUINFO + SET UPPER_MENU_NO = 9100030, MENU_ORDR = 10, + MENU_NM = '접속기록 조회 및 감사', + MENU_DC = '개인정보 접속·처리기록 조회, 이상행위 감사, 다운로드(사유·이력)' + WHERE MENU_NO = 9100044; -INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL) -SELECT * FROM ( - SELECT 'AccessLogPurgeHistory' AS PROGRM_FILE_NM, '/mnt/sec/purgeHistory/' AS PROGRM_STRE_PATH, - '접속기록 파기이력' AS PROGRM_KOREAN_NM, - '접속기록 파티션 파기·증설 이력 조회(SER-002-2)' AS PROGRM_DC, - '/mnt/sec/purgeHistory/listView.do?page=9100050' AS URL -) seed -WHERE NOT EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = seed.PROGRM_FILE_NM); +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, @@ -1447,110 +1514,27 @@ SELECT * FROM ( ) 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, 'InspectionReportList' AS PROGRM_FILE_NM, 9100048 AS MENU_NO, - 9100030 AS UPPER_MENU_NO, 14 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 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, 'AbnormalAccessList' AS PROGRM_FILE_NM, 9100049 AS MENU_NO, - 9100030 AS UPPER_MENU_NO, 15 AS MENU_ORDR, '비정상 접근 탐지 5유형 조회' 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 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, 'AccessLogPurgeHistory' AS PROGRM_FILE_NM, 9100050 AS MENU_NO, - 9100030 AS UPPER_MENU_NO, 16 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 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, '암호화 회원정보 복호화 조회·수정·파기·등록' AS MENU_DC, + 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 n.mno, g.ac - FROM (SELECT 9100048 AS mno UNION ALL SELECT 9100049 UNION ALL - SELECT 9100050 UNION ALL SELECT 9100051) n - CROSS JOIN (SELECT 'IS_AUTHENTICATED_FULLY' AS ac UNION ALL SELECT 'ROLE_ADMIN') g +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 = n.mno AND x.AUTHOR_CODE = g.ac); + 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); --- ── [8] "접속기록 · 감사" 그룹 재편(9100052) — 2026-08-27, 제안서 Ⅲ-4 시안 정합 ── --- [왜] 제안요약서 slide 14~15 의 화면 시안이 좌측메뉴를 "접속기록 · 감사" 한 묶음으로 그리고 --- 그 아래 4개(접속기록 조회 / 개인정보 처리기록 / 이상행위 감사 / 정기 점검 리포트)를 둔다. --- 위 [7]은 이 넷을 권한관리(9100030) 하위에 낱개로 붙여 시안과 어긋났다 — 여기서 되돌린다. --- [순서] 반드시 [7] 뒤에 온다([7]이 만든 9100048·9100049 를 이설하기 때문). --- 신규 설치는 [7]→[8]을 이어 실행하면 최종 구조에 도달하고, --- 이미 [7]만 적용된 DB는 [8]만 실행해도 같은 결과가 된다(전 구간 멱등). --- [내려가는 것] 접속기록 파기이력(9100050)은 시안에 메뉴가 없다. 메뉴만 제거하고 화면· --- 컨트롤러·프로그램(URL) 등록은 남긴다 — 정기 점검 리포트 화면의 버튼으로 진입한다. --- [그룹 메뉴 규약] PROGRM_FILE_NM 은 NOT NULL 이고 기존 그룹이 모두 센티넬 'dir' 을 쓴다 --- (COMTNPROGRMLIST 에 URL='dir' 1행). NULL 을 넣으면 ERROR 1048 로 실패한다. --- ★ left.jsp 동반 배포 필수 — 9100052 그룹 블록 신설 + 권한관리 블록에서 4개 분기 제거 - -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, 'dir' AS PROGRM_FILE_NM, 9100052 AS MENU_NO, - 9100000 AS UPPER_MENU_NO, 5 AS MENU_ORDR, - '개인정보 접속·처리기록 조회 및 감사(제안서 Ⅲ-4)' AS MENU_DC, - NULL AS RELATE_IMAGE_PATH, NULL AS RELATE_IMAGE_NM -) seed -WHERE NOT EXISTS (SELECT 1 FROM COMTNMENUINFO m WHERE m.MENU_NO = seed.MENU_NO) - AND EXISTS (SELECT 1 FROM COMTNPROGRMLIST p WHERE p.PROGRM_FILE_NM = 'dir'); - -INSERT INTO COMTNPROGRMLIST (PROGRM_FILE_NM, PROGRM_STRE_PATH, PROGRM_KOREAN_NM, PROGRM_DC, URL) -SELECT * FROM ( - SELECT 'ProcessLogList' AS PROGRM_FILE_NM, '/mnt/sec/processLog/' AS PROGRM_STRE_PATH, - '개인정보 처리기록' AS PROGRM_KOREAN_NM, - '개인정보를 실제로 변경한 기록(등록·수정·삭제)만 조회' AS PROGRM_DC, - '/mnt/sec/processLog/listView.do?page=9100053' 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, 'ProcessLogList' AS PROGRM_FILE_NM, 9100053 AS MENU_NO, - 9100052 AS UPPER_MENU_NO, 2 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); - -UPDATE COMTNMENUINFO SET UPPER_MENU_NO = 9100052, MENU_ORDR = 1, MENU_NM = '접속기록 조회' - WHERE MENU_NO = 9100044; -UPDATE COMTNMENUINFO SET UPPER_MENU_NO = 9100052, MENU_ORDR = 3, MENU_NM = '이상행위 감사', - MENU_DC = '비정상 접근 5유형 탐지 결과 조회' - WHERE MENU_NO = 9100049; -UPDATE COMTNMENUINFO SET UPPER_MENU_NO = 9100052, MENU_ORDR = 4, MENU_NM = '정기 점검 리포트' - WHERE MENU_NO = 9100048; - -INSERT INTO COMTNMENUCREATDTLS (MENU_NO, AUTHOR_CODE) -SELECT n.mno, g.ac - FROM (SELECT 9100052 AS mno UNION ALL SELECT 9100053) n - CROSS JOIN (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 = n.mno AND x.AUTHOR_CODE = g.ac); - -DELETE FROM COMTNMENUCREATDTLS WHERE MENU_NO = 9100050; -DELETE FROM COMTNMENUINFO WHERE MENU_NO = 9100050; - --- [검증] SELECT m.MENU_NO,m.UPPER_MENU_NO,m.MENU_ORDR,m.MENU_NM,HEX(m.MENU_NM) --- FROM COMTNMENUINFO m WHERE m.MENU_NO=9100052 OR m.UPPER_MENU_NO=9100052 --- ORDER BY m.UPPER_MENU_NO, m.MENU_ORDR; +-- [검증] 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; -- =====================================================================