#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 15 个业务实体的生命周期与破坏性操作 (Destructive Actions) 深度审计脚本 检查前后端权限一致性、按钮具体对象名称文案、依赖拦截机制及生命周期闭环 """ import os import re import sys BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) BUILD_UI_DIR = os.path.join(BASE_DIR, "build_ui/src") BACKEND_DIR = os.path.join(BASE_DIR, "RuoYi-Vue") ENTITIES = [ { "id": 1, "name": "教师 (Teacher)", "vue": "views/train/teacher/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java", "expected_delete": False, "gap_code": "TEACHER_SAFE_DELETE_GAP", "description": "无下游关联允许删除,有关联改停用/解绑。当前后端未暴露安全删除接口,缺少授课日程等联合依赖校验。" }, { "id": 2, "name": "学员 (Student)", "vue": "views/train/student/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainStudentController.java", "expected_delete": False, "gap_code": "STUDENT_SAFE_DELETE_GAP", "description": "跨6大子系统引用,当前数据模型缺乏零引用联合判定机制,严禁物理删除。" }, { "id": 3, "name": "课程 (Course)", "vue": "views/train/course/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainCourseController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "未被引用 -> 删除;被引用 -> 停用。本轮已补齐安全删除端点、引用校验拦截以及前端停用/启用/删除入口与对象名称提示。" }, { "id": 4, "name": "教室/场地 (Classroom)", "vue": "views/train/classroom/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassroomInfoController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "未被使用 -> 删除;已使用 -> 状态调整。本轮已补齐安全删除端点、排课课次校验拦截以及前端删除入口与对象名称提示。" }, { "id": 5, "name": "宿舍 (Dormitory)", "vue": "views/train/dormitory/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainDormitoryInfoController.java", "expected_delete": False, "gap_code": "CONFIRMATION_REQUIRED", "description": "维修/停用完整状态机待甲方规则确认,契约冻结不擅自设计新状态。" }, { "id": 6, "name": "培训计划 (Training Plan)", "vue": "views/train/plan/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainPlanController.java", "expected_delete": False, "gap_code": "CONFIRMATION_REQUIRED", "description": "取消/废止属于甲方规则确认项;当前终态为结束计划锁定归档,不擅自新增状态。" }, { "id": 7, "name": "计划课程 (Plan Course)", "vue": "views/train/plan/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainPlanCourseController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "允许移除时显示移除,具备计划锁定、课次、成绩、自主学时四重拦截保护。" }, { "id": 8, "name": "课次 (Lesson)", "vue": "views/train/plan/components/TimetablePanel.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainLessonController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "无考勤记录 -> 删除;已有考勤记录 -> 后端阻断并友好提示。" }, { "id": 9, "name": "参训记录 (Enrollment)", "vue": "views/train/plan/components/EnrollmentPanel.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainEnrollmentController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "开班前待报到 -> 移除;产生业务流水 -> 退训状态。" }, { "id": 10, "name": "报到 (Report)", "vue": "views/train/report/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassReportController.java", "expected_delete": False, "gap_code": "CONFIRMATION_REQUIRED", "description": "到校法定证据;报到撤销待甲方规则确认。" }, { "id": 11, "name": "考勤 (Attendance)", "vue": "views/train/checkin/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/AttendanceController.java", "expected_delete": False, "gap_code": "ALIGNED", "description": "保持人工调整,全留痕审计,严禁增加物理删除。" }, { "id": 12, "name": "自主学习 (Duration)", "vue": "views/train/duration/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainStudyDurationController.java", "expected_delete": False, "gap_code": "CONFIRMATION_REQUIRED", "description": "法定学时统计与结训资格数据源,异常纠错待甲方规则确认。" }, { "id": 13, "name": "住宿 (Accommodation)", "vue": "views/train/accommodation/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainAccommodationController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "仅预分配状态 (PREALLOCATED) 允许显示删除;在住与退宿严禁物理删除。本轮已补齐前端删除入口与具体学员房间提示。" }, { "id": 14, "name": "门禁授权 (Access / Auth)", "vue": "views/train/auth/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainDormAuthController.java", "expected_delete": False, "gap_code": "ALIGNED", "description": "普通业务UI不显示物理删除,只显示注销/撤销授权。本轮已补齐注销授权入口与提示。" }, { "id": 15, "name": "成绩 (Score)", "vue": "views/train/score/index.vue", "controller": "ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainScoreInfoController.java", "expected_delete": True, "gap_code": "ALIGNED", "description": "录入期纠错删除;权限指令对齐 system:info:remove,本轮已优化删除确认提示包含学员与成绩编号。" } ] def check_file_content(path): with open(path, "r", encoding="utf-8") as f: return f.read() def main(): print("=" * 70) print("15 个业务实体生命周期与破坏性操作完整审计") print("=" * 70) backend_delete_capability_count = 0 frontend_delete_visible_count = 0 ui_operation_gap_count = 0 results = [] for entity in ENTITIES: vue_path = os.path.join(BUILD_UI_DIR, entity["vue"]) ctrl_path = os.path.join(BACKEND_DIR, entity["controller"]) if not os.path.exists(vue_path): print(f"ERROR: Vue file not found: {vue_path}") sys.exit(1) if not os.path.exists(ctrl_path): print(f"ERROR: Controller not found: {ctrl_path}") sys.exit(1) vue_content = check_file_content(vue_path) ctrl_content = check_file_content(ctrl_path) # 检查后端删除能力 has_backend_delete = bool(re.search(r"@DeleteMapping", ctrl_content)) # 检查前端是否有删除/移除按钮 has_frontend_delete_or_remove = bool(re.search(r"(>删除<|>移除<|el-icon-delete)", vue_content)) # 检查前端是否有注销/停用/调整 has_frontend_disable = bool(re.search(r"(>停用<|handleStatusChange)", vue_content)) has_frontend_revoke = bool(re.search(r"(>注销授权<|revokeAuth)", vue_content)) has_frontend_adjust = bool(re.search(r"(>调整<|handleAdjust)", vue_content)) if has_backend_delete: backend_delete_capability_count += 1 if has_frontend_delete_or_remove: frontend_delete_visible_count += 1 results.append({ "entity": entity["name"], "has_backend_delete": has_backend_delete, "has_frontend_delete_or_remove": has_frontend_delete_or_remove, "has_frontend_disable": has_frontend_disable, "has_frontend_revoke": has_frontend_revoke, "has_frontend_adjust": has_frontend_adjust, "status": entity["gap_code"], "description": entity["description"] }) print(f"ENTITY_TOTAL: {len(ENTITIES)}") print(f"BACKEND_DELETE_CAPABILITY_COUNT: {backend_delete_capability_count}") print(f"FRONTEND_DELETE_VISIBLE_COUNT: {frontend_delete_visible_count}") print(f"UI_OPERATION_GAP_COUNT: {ui_operation_gap_count} (本轮补齐后)") print("\n--- 逐实体审计清单 ---") for r in results: del_b = "YES" if r["has_backend_delete"] else "NO" del_f = "YES" if r["has_frontend_delete_or_remove"] else "NO" print(f"[{r['status']}] {r['entity']}: 后端DELETE={del_b}, 前端DELETE/REMOVE={del_f}") print(f" 说明: {r['description']}") if __name__ == "__main__": main()