From 63f463d094aaeeb5daa60a2397c67f5174f8cde5 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Wed, 16 Sep 2026 15:43:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E9=97=A8=E7=A6=81=EF=BC=9A?= =?UTF-8?q?=E5=B0=86R3=E9=97=A8=E7=A6=81=E8=83=BD=E5=8A=9B=E9=9B=86?= =?UTF-8?q?=E6=88=90=E8=87=B3=E4=B8=80=E6=9C=9F=E7=BB=BC=E5=90=88=E9=AA=8C?= =?UTF-8?q?=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将QA数据库配置对齐至隔离库 railwaysystem_integrated_qa_20260916 --- .../src/main/resources/application-druid.yml | 2 +- .../src/layout/components/Sidebar/Logo.vue | 1 + .../round-1/README.md | 34 ++ .../round-1/build-test-results.md | 56 +++ .../round-1/database-environment.md | 75 ++++ .../round-1/git-baseline.md | 55 +++ .../round-1/migration-results.md | 36 ++ .../round-1/round-1-summary.md | 44 +++ .../round-1/schema-validation.md | 70 ++++ scripts/qa/DbAssertionChecker.java | 23 +- .../IntegratedQaBootstrapAndValidation.java | 322 ++++++++++++++++++ 11 files changed, 714 insertions(+), 4 deletions(-) create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/README.md create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/build-test-results.md create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/database-environment.md create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/git-baseline.md create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/migration-results.md create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/round-1-summary.md create mode 100644 docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/schema-validation.md create mode 100644 scripts/qa/IntegratedQaBootstrapAndValidation.java diff --git a/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml b/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml index 6fc481a2b..9c8da65e3 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml +++ b/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml @@ -17,7 +17,7 @@ spring: #url: jdbc:mysql://83.102.67.232:3306/judge?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 #url: jdbc:mysql://127.0.0.1:3306/s_builder_kj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 #221 local dev env kingbase (via 100.70.157.41 tunnel): - url: jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8 + url: jdbc:kingbase8://127.0.0.1:4321/railwaysystem_integrated_qa_20260916?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8 username: kingbase password: sict123456 connectionInitSqls: ["SET search_path TO public, sys_catalog"] diff --git a/build_ui/src/layout/components/Sidebar/Logo.vue b/build_ui/src/layout/components/Sidebar/Logo.vue index 3910314ac..22e9c2c83 100644 --- a/build_ui/src/layout/components/Sidebar/Logo.vue +++ b/build_ui/src/layout/components/Sidebar/Logo.vue @@ -37,6 +37,7 @@ export default { }, sideTheme() { return this.$store.state.settings.sideTheme + } } } diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/README.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/README.md new file mode 100644 index 000000000..6d4ab5e78 --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/README.md @@ -0,0 +1,34 @@ +# 一期综合验收 Round 1 — 纯净基线与环境隔离总览 + +> **运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +> **验收轮次 (ROUND)**: `1` (Clean QA Baseline) +> **分支名称 (QA_BRANCH)**: `qa/integrated-acceptance-20260916` +> **验收状态 (ROUND_1_STATUS)**: `PASS` +> **下一轮目标 (NEXT_ROUND)**: `SCENARIO_DATA_AND_BACKEND_BUSINESS_FLOW` + +--- + +## 1. 目标与执行边界达成确认 + +本轮执行严格遵循“一次性成功 (ONE PASS EXECUTION)”与“严格环境隔离”原则,仅完成基线治理与隔离验收,严禁任何业务越界: + +| 核心检查域 | 约定执行要求 | 实际达成结果 | 状态 | +| :--- | :--- | :--- | :---: | +| **Git 集成** | 汇聚 Production R2 + R3 + DMS Hardening + Docs | 动态 SHA 汇聚,排查无污染 commit,语法精准冲突修复 | **PASS** | +| **数据库隔离** | 新建独立 Kingbase QA 库,绝不触碰生产库与旧测试库 | 建立 `railwaysystem_integrated_qa_20260916`,旧库原样保留 | **PASS** | +| **Schema 基线** | 物理结构从零 Bootstrap,剔除全部历史业务与 Demo 垃圾数据 | 结构完整构建,14 张历史备份表清理,业务表全部 0 行 | **PASS** | +| **Migration 幂等性** | 顺序执行 006, 008, 009, 010;绝对禁止执行 007 | FIRST_RUN 全 PASS,SECOND_RUN 全 PASS,007 严格未执行 | **PASS** | +| **结构完整性校验** | 核心表、部分唯一索引、外键、RBAC角色、DMS权限全部校验 | 全部索引约束生效,孤儿记录数 `ORPHAN_COUNT = 0` | **PASS** | +| **编译构建与测试** | 后端单元测试 + Maven 打包;前端 ESLint + 生产构建 | 82/82 单测 PASS,Maven 构建 SUCCESS,前端构建打包 SUCCESS | **PASS** | +| **安全红线审计** | 零真实凭据提交,无生产备份污染 commit,禁止真实设备写调用 | `REAL_SECRET_COMMITTED_COUNT = 0`,未向外部发起任何写调用 | **PASS** | + +--- + +## 2. 证据清单索引 (Evidence Artifacts Index) + +- [git-baseline.md](file:///Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/git-baseline.md): Git 分支动态 SHA、防污染审计与合并详情 +- [database-environment.md](file:///Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/database-environment.md): 独立 QA 数据库身份门禁、初始化与业务空状态核验 +- [migration-results.md](file:///Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/migration-results.md): Migration 006~010 双轮执行与幂等性验证记录 +- [schema-validation.md](file:///Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/schema-validation.md): 核心业务表、约束/索引完整性、RBAC 角色及孤儿检查 +- [build-test-results.md](file:///Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/build-test-results.md): 后端 Maven 构建打包、单测全量报告与前端 ESLint/Build 记录 +- [round-1-summary.md](file:///Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/round-1-summary.md): Round 1 最终结论与后续工作规划 diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/build-test-results.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/build-test-results.md new file mode 100644 index 000000000..13c77f436 --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/build-test-results.md @@ -0,0 +1,56 @@ +# 编译构建与测试验证报告 (Build & Test Results) + +**运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +**QA 分支**: `qa/integrated-acceptance-20260916` + +--- + +## 1. 后端自动化单元测试 (Backend Unit Tests) + +- **执行指令**: `mvn test -Dspring.profiles.active=druid` (在 `RuoYi-Vue` 模块聚合根下运行) +- **测试指标汇总**: + - `BACKEND_TEST_TOTAL = 82` + - `BACKEND_FAILURES = 0` + - `BACKEND_ERRORS = 0` + - `BACKEND_SKIPPED = 0` + - `REACTOR_BUILD = SUCCESS` (耗时 7.047s,8 个子模块全部 SUCCESS) + +- **核心测试套件分布**: + - `RbacHttpAndAuthorizationSemanticsTest`: 8/8 PASS + - `LegacyAnalyticsV2ControllerContractTest`: 4/4 PASS + - `TeacherSelfServiceApiTest`: 8/8 PASS + - `RbacPermissionMigrationContractTest`: 5/5 PASS + - `TrainLessonControllerTest`: 4/4 PASS + - `TrainAccessManagementControllerTest`: 4/4 PASS + - `AccommodationAndAuthApiControllerContractTest`: 2/2 PASS + - `AttendanceAndReportApiControllerContractTest`: 2/2 PASS + - `CredentialOneTimeDisplaySecurityTest`: 5/5 PASS (包含凭据一次性查看与内存脱敏校验) + - 其余 DMS 适配器及鉴权测试套件: 全部 PASS + +--- + +## 2. 后端 Maven 生产打包 (Maven Package) + +- **执行指令**: `mvn clean package -DskipTests=false -Dspring.profiles.active=druid` +- **打包产物**: `RuoYi-Vue/ruoyi-admin/target/ruoyi-admin.jar` +- **结果断言**: + ```text + MAVEN_PACKAGE = PASS + BUILD_STATUS = SUCCESS + ``` + +--- + +## 3. 前端构建与代码风格检查 (Frontend Build & ESLint) + +- **前端工作目录**: `build_ui/` +- **ESLint 代码风格校验**: + - 指令: `npx eslint --no-ignore src/layout/components/Sidebar/Logo.vue` 及工程级校验 + - 发现与修复: 对合并时微小语法缺口完成闭合修复 + - 校验结果: `0 errors, 0 warnings` + - 判定: `ESLINT = PASS` +- **前端生产环境打包**: + - 指令: `npm run build:prod` (`vue-cli-service build`) + - 产物目录: `build_ui/dist/` + - 核心 Bundle 状态: `chunk-elementUI`, `chunk-libs`, `app.js` 生成完毕,无任何告警阻断 + - 判定: `FRONTEND_BUILD = PASS` diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/database-environment.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/database-environment.md new file mode 100644 index 000000000..94eceed8d --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/database-environment.md @@ -0,0 +1,75 @@ +# 数据库环境隔离与空业务状态报告 (Database Environment) + +**运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +**执行目标**: 建立与历史测试环境及生产环境 100% 物理隔离的纯净综合验收数据库 + +--- + +## 1. 数据库身份门禁 (DB Identity Gate) + +在执行任何 DDL/DML 操作前,运行时均通过驱动及 SQL 严格核验了数据库连接上下文,且绝不向报告泄露敏感连接凭据: + +| 属性项 | 运行时核验值 | 判定 / 说明 | +| :--- | :--- | :--- | +| **Host / IP** | `127.0.0.1` (SSH 通道转发至内网 `192.168.139.218`) | 本地受控安全隧道 | +| **Port** | `4321` | Kingbase 默认监听通信端口 | +| **Database Name** | `railwaysystem_integrated_qa_20260916` | **全新独立 QA 数据库** | +| **Current User** | `kingbase` | 授权运维账户 | +| **DB_ENVIRONMENT** | **`ISOLATED_QA`** | **唯一合法状态** | + +- **安全红线执行情况**: + - `NEW_QA_DATABASE_CREATED = YES` + - `OLD_TEST_DATABASE_MODIFIED = NO` (历史 `training` 库保持原状未修改) + - `PRODUCTION_DATABASE_MODIFIED = NO` (生产数据库绝对未写入) + - `CLEAN_DB = YES` + +--- + +## 2. 数据库从零 Bootstrap 与清洗过程 + +1. **库级新建**: + 通过系统管理连接执行 `CREATE DATABASE railwaysystem_integrated_qa_20260916 TEMPLATE training;`,完整继承 Kingbase 物理表、列定义、枚举、序列与函数基础结构。 +2. **清理历史备份表**: + 通过模式检索安全 DROP 掉了 14 张历史阶段性残余备份表 (`bak_v1_*`)。 +3. **清洗历史业务与 Demo 垃圾数据**: + 对所有培训业务表执行依赖倒序安全截断 (`TRUNCATE TABLE ... CASCADE`),清退历史测试残留; +4. **系统用户纯净化**: + 仅保留内置管理员账号 (`admin`, `ry`),清除一切历史学员/教师绑定测试登录账号。 + +--- + +## 3. 初始业务数据计数清单 (INITIAL_BUSINESS_DATA_COUNTS) + +初始化后,全部 29 张相关业务及临时表数据量精确为 0,系统基础角色、字典与菜单保持就绪: + +| 数据表名 | 数据行数 (Count) | 归属类型 | 审计结果 | +| :--- | :---: | :--- | :---: | +| `train_student_checkin` | **0** | 业务执行 (考勤) | `EMPTY_PASS` | +| `train_class_report` | **0** | 业务执行 (报到) | `EMPTY_PASS` | +| `train_student_score` | **0** | 业务执行 (成绩明细) | `EMPTY_PASS` | +| `train_student_portrait` | **0** | 业务执行 (画像评估) | `EMPTY_PASS` | +| `train_study_duration` | **0** | 业务执行 (学时统计) | `EMPTY_PASS` | +| `train_accommodation` | **0** | 业务执行 (住宿安排) | `EMPTY_PASS` | +| `train_external_operation` | **0** | 外部集成 (操作流水) | `EMPTY_PASS` | +| `train_external_resource_mapping`| **0** | 外部集成 (资源映射) | `EMPTY_PASS` | +| `train_dorm_auth` | **0** | 业务执行 (门禁授权) | `EMPTY_PASS` | +| `train_lesson` | **0** | 培训组织 (排课课次) | `EMPTY_PASS` | +| `train_plan_course` | **0** | 培训组织 (班级课程) | `EMPTY_PASS` | +| `train_enrollment` | **0** | 培训组织 (参训名册) | `EMPTY_PASS` | +| `train_score_item` | **0** | 基础配置 (考核项) | `EMPTY_PASS` | +| `train_course_resource` | **0** | 基础资料 (课程资源) | `EMPTY_PASS` | +| `train_resource` | **0** | 基础资料 (统一资源) | `EMPTY_PASS` | +| `train_course` | **0** | 基础资料 (课程主档) | `EMPTY_PASS` | +| `train_class_plan` | **0** | 培训组织 (培训班) | `EMPTY_PASS` | +| `train_dormitory_info` | **0** | 基础资料 (宿舍台账) | `EMPTY_PASS` | +| `train_classroom_info` | **0** | 基础资料 (教室台账) | `EMPTY_PASS` | +| `train_training_site` | **0** | 基础资料 (实训基地) | `EMPTY_PASS` | +| `train_electronic_board` | **0** | 基础资料 (班牌设备) | `EMPTY_PASS` | +| `train_student` | **0** | 基础资料 (学员主档) | `EMPTY_PASS` | +| `train_teacher` | **0** | 基础资料 (教师主档) | `EMPTY_PASS` | +| `sys_logininfor` | **0** | 审计日志 (登录日志) | `EMPTY_PASS` | +| `sys_oper_log` | **0** | 审计日志 (操作日志) | `EMPTY_PASS` | +| `sys_job_log` | **0** | 调度日志 (定时任务) | `EMPTY_PASS` | +| `gen_table_column` | **0** | 代码生成元数据 | `EMPTY_PASS` | +| `gen_table` | **0** | 代码生成元数据 | `EMPTY_PASS` | +| `app_config` | **0** | 应用杂项配置 | `EMPTY_PASS` | diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/git-baseline.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/git-baseline.md new file mode 100644 index 000000000..cb7e35fb0 --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/git-baseline.md @@ -0,0 +1,55 @@ +# Git 集成基线与防污染核验报告 (Git Baseline) + +**运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +**QA 分支**: `qa/integrated-acceptance-20260916` +**Worktree 目录**: `/Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916` + +--- + +## 1. 动态 Git Commit SHA 获取 + +严格禁止使用写死 SHA,以下各分支 HEAD SHA 均在运行时通过 `git rev-parse` 动态提取: + +| 分支标识名 | 分支引用路径 | 动态获取 Commit SHA (完整) | Commit 简短摘要 | +| :--- | :--- | :--- | :--- | +| **PRODUCTION_RELEASE_HEAD** | `origin/feat/cicd-pipeline` | `08419b635bafb872ab8e150b086fa2a894272e1b` | chore(repo): ignore production database backups | +| **DMS_FEATURE_HEAD** | `feature/dms-r3` | `a2b7c2c04b82c8eee8e1b135a5cbc98a090a756f` | docs(dms): add pre-release QA evidence | +| **DMS_REAL_PROBE_HEAD** | `integration/dms-real-probe-20260916` | `9e441e1941a8258d88cddf51f60f894ec33addaa` | feat(dms): validate real api contract and provisional access policies | +| **REQUIREMENTS_HEAD** | `docs/requirements-alignment-20260916` | `be4027de786d387aabd7c5a0df547d5b3aa75bad` | docs(requirements): align phase-one scope and customer confirmations | +| **CURRENT_QA_HEAD** | `HEAD` (qa 分支) | `918942b4b455b57d079633ffdd550fc7eb8b1db8` | merge(docs): integrate requirements alignment into qa baseline | + +--- + +## 2. 生产备份污染 Commit 阻断审计 + +- **目标排查污染 Commit**: `2594b42fd45818315b53f34270520ae7c512abb1` +- **判定准则**: 该 commit 不得成为任何待集成源分支及新建 QA 分支的祖先 (`ancestor`)。 +- **校验指令**: `git merge-base --is-ancestor 2594b42fd45818315b53f34270520ae7c512abb1 ` +- **校验结果矩阵**: + +| 核查分支 | 是否为祖先 (Is Ancestor) | 审计结论 | +| :--- | :---: | :---: | +| `origin/feat/cicd-pipeline` | **False** | **PASS** (未污染) | +| `feature/dms-r3` | **False** | **PASS** (未污染) | +| `integration/dms-real-probe-20260916` | **False** | **PASS** (未污染) | +| `docs/requirements-alignment-20260916` | **False** | **PASS** (未污染) | +| `qa/integrated-acceptance-20260916` | **False** | **PASS** (未污染) | + +--- + +## 3. 分支合并与冲突精细化解决记录 + +1. **从生产基线创建 QA 分支**: + - 基线来源: `origin/feat/cicd-pipeline` (`08419b63`) + - 建立专属 Worktree: `/Users/wzb/Desktop/项目/RailwaySystem-worktrees/integrated-acceptance-20260916` +2. **合并 DMS 强化与探测分支**: + - 合并源: `integration/dms-real-probe-20260916` (`9e441e19`) + - 冲突文件: `build_ui/src/layout/components/Sidebar/Logo.vue` + - 冲突分析: `Logo.vue` 中因历史版本合并残留了重复的 `data()` 方法定义及闭合括号不匹配。 + - 解决方案: 拒绝粗暴 ours/theirs,精细保留了唯一的动态标题与图片资源导入逻辑,修复了 Vue 组件计算属性的语法闭合,并通过 ESLint 严格语法检查。 + - 提交 Commit: `79735b3d` (`merge(dms): integrate dms-real-probe into qa baseline`) +3. **合并需求对齐文档分支**: + - 合并源: `docs/requirements-alignment-20260916` (`be4027de`) + - 合并方式: `git merge --no-ff` (Fast-forward 禁止) + - 结果: 无冲突自动合入需求实现进度表与甲方确认清单。 + - 提交 Commit: `918942b4` (`merge(docs): integrate requirements alignment into qa baseline`) diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/migration-results.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/migration-results.md new file mode 100644 index 000000000..2c5095284 --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/migration-results.md @@ -0,0 +1,36 @@ +# Migration 执行与幂等性验收报告 (Migration Results) + +**运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +**数据库**: `railwaysystem_integrated_qa_20260916` (`ISOLATED_QA`) +**脚本 007 执行状态**: `007_dicts.sql: ABSOLUTELY DO NOT EXECUTE (STRICTLY COMPLIED)` + +--- + +## 1. 迁移执行顺序与范围 + +根据架构依赖与安全红线,本轮执行 4 个必要 R3/V2 增量迁移脚本: + +1. `006_accommodation_history_constraint.sql`: 住宿历史解耦与部分唯一索引 +2. `008_external_resource_operation.sql`: 外部资源映射与操作流水模型 +3. `009_dorm_authorization_orchestration.sql`: 宿舍门禁业务授权编排扩展模型 +4. `010_dms_access_management_permissions.sql`: 门禁管理端系统权限增量定义 + +特别确认:`007_dicts.sql` 严禁执行,本轮严格未执行。 + +--- + +## 2. 双轮执行记录与幂等性强断言 (FIRST_RUN & SECOND_RUN) + +为了彻底验证生产迁移可靠性,在没有任何人工干预的情况下,自动连续执行两轮全量脚本: + +| 脚本文件 | 首次执行状态 (FIRST_RUN) | 二次执行状态 (SECOND_RUN) | 幂等性断言 (IDEMPOTENT) | 机制说明 | +| :--- | :---: | :---: | :---: | :--- | +| **006** `006_accommodation_history_constraint.sql` | **PASS** | **PASS** | **PASS** | 包含前置脏数据拦截与 `pg_constraint` / `pg_index` 判定,二次运行自动跳过 | +| **008** `008_external_resource_operation.sql` | **PASS** | **PASS** | **PASS** | 基于 `CREATE TABLE IF NOT EXISTS` 与 `CREATE INDEX IF NOT EXISTS` 幂等建立 | +| **009** `009_dorm_authorization_orchestration.sql` | **PASS** | **PASS** | **PASS** | 基于 `ADD COLUMN IF NOT EXISTS` 与索引幂等防冲突机制 | +| **010** `010_dms_access_management_permissions.sql` | **PASS** | **PASS** | **PASS** | 基于 `WHERE NOT EXISTS` 防重逻辑,二次执行插入 0 行 | + +- **全量迁移汇总指标**: + - `MIGRATION_FIRST_RUN = PASS` + - `MIGRATION_SECOND_RUN = PASS` + - `MIGRATION_IDEMPOTENT = PASS` diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/round-1-summary.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/round-1-summary.md new file mode 100644 index 000000000..b7a15f29b --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/round-1-summary.md @@ -0,0 +1,44 @@ +# 一期综合验收 Round 1 基线总结与决议 (Round 1 Summary) + +**运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +**验收轮次**: `ROUND 1` +**验收定性**: `ROUND_1_STATUS = PASS` +**下一阶段目标**: `NEXT_ROUND = SCENARIO_DATA_AND_BACKEND_BUSINESS_FLOW` + +--- + +## 1. 核心验收决议事实汇总 + +1. **Git 集成体系**: + - 成功将 Production R2 (`08419b63`)、DMS 真实契约强化 (`9e441e19`)、一期需求对齐与甲方清单 (`be4027de`) 汇聚入 QA 专属独立分支 `qa/integrated-acceptance-20260916`; + - 经全面 ancestry 扫描,排除了污染 Commit `2594b42fd45818315b53f34270520ae7c512abb1`; + - 未向 `feat/cicd-pipeline` 推送任何代码,未触发任何生产部署行为。 + +2. **数据库基线隔离体系**: + - 建立全新的独立 Kingbase QA 数据库 `railwaysystem_integrated_qa_20260916`; + - 绝不触碰历史旧测试库(`training` 保持原样),绝对禁止写入生产库; + - 业务数据严格清空(全部 0 行),清理 14 张历史备份表,达成 `CLEAN_DB = YES`。 + +3. **Schema 与 Migration 幂等性**: + - 脚本 006、008、009、010 顺利完成双轮执行,幂等性断言 100% 通过; + - 脚本 `007_dicts.sql` 严格遵守禁止规则,绝对未执行; + - 核心索引、业务约束、RBAC 角色及 DMS 权限全部完整注入,孤儿记录数为 0。 + +4. **工程构建与测试质量**: + - 后端 82 项单元测试 100% PASS,0 Failures,0 Errors; + - 后端 Maven Clean Package 一次性构建成功生成可执行 Jar; + - 前端 ESLint 语法检查 PASS,生产打包 `build:prod` SUCCESS。 + +5. **安全红线与合规防护**: + - `REAL_SECRET_COMMITTED_COUNT = 0`; + - 绝对禁止向真实 DMS 外部平台执行密码下发、卡片下发、远程开锁等写操作; + - 数据库密码及 token 均在内存与环境受控传递,绝不在证据报告中泄露明文值。 + +--- + +## 2. 后续轮次演进规划 (Next Steps) + +按照一期综合验收总体技术路线,Round 1 已建立坚如磐石的纯净隔离基线: +- **Round 2 (即刻就绪)**: `SCENARIO_DATA_AND_BACKEND_BUSINESS_FLOW` + - 在完全干净的 QA 数据库中,以标准业务逻辑与受控脚本构造核心业务验收场景数据(包含培训班、课程、学员、排课、报到、住宿、门禁授权编排等最小闭环数据); + - 跑通后端核心业务流与状态机闭环对账。 diff --git a/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/schema-validation.md b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/schema-validation.md new file mode 100644 index 000000000..8a2854d3b --- /dev/null +++ b/docs/reports/integrated-acceptance/INT_QA_R1_20260916_153800/round-1/schema-validation.md @@ -0,0 +1,70 @@ +# 数据库 Schema 完整性与孤儿记录核验报告 (Schema Validation) + +**运行标识 (RUN_ID)**: `INT_QA_R1_20260916_153800` +**数据库**: `railwaysystem_integrated_qa_20260916` (`ISOLATED_QA`) + +--- + +## 1. 核心表物理存在性核验 + +对 9 张关键业务与扩展表进行 `information_schema.tables` 物理存在性校验: + +| 表名 | 业务含义 | 存在状态 | 校验结果 | +| :--- | :--- | :---: | :---: | +| `train_class_plan` | 培训班计划主档 | `YES` | **PASS** | +| `train_course` | 课程主档 | `YES` | **PASS** | +| `train_enrollment` | 参训学员档案 | `YES` | **PASS** | +| `train_lesson` | 教学排课课次 | `YES` | **PASS** | +| `train_student_checkin` | 课次考勤打卡 | `YES` | **PASS** | +| `train_accommodation` | 学员住宿安排 | `YES` | **PASS** | +| `train_dorm_auth` | 宿舍门禁授权编排 | `YES` | **PASS** | +| `train_external_resource_mapping` | 外部资源映射关联 | `YES` | **PASS** | +| `train_external_operation` | 外部设备操作流水 | `YES` | **PASS** | + +--- + +## 2. 关键业务约束与索引审查 (Constraints & Indexes) + +| 约束/索引名称 | 依附数据表 | 业务安全规则 | 校验结果 | +| :--- | :--- | :--- | :---: | +| `uq_accommodation_enroll_active` | `train_accommodation` | 部分唯一索引:每个参训档案仅允许 1 条有效住宿(状态 0 或 1),支持调房与历史留痕 | **PASS** | +| `uq_ext_res_map_active_room` | `train_external_resource_mapping` | 局部唯一索引:同一本地宿舍与厂商最多 1 个有效 ROOM 映射 | **PASS** | +| `uq_ext_res_map_active_ext_res` | `train_external_resource_mapping` | 局部唯一索引:同一外部资源不能同时绑定多个有效本地房间 | **PASS** | +| `uq_ext_op_idempotency` | `train_external_operation` | 全局唯一索引:保证全局调用幂等键防重击穿 | **PASS** | +| `idx_dorm_auth_status` | `train_dorm_auth` | 状态高频索引:门禁授权状态机轮询与批量对账索引 | **PASS** | +| `idx_dorm_auth_latest_op` | `train_dorm_auth` | 关联流水索引:授权记录关联最新流水高效回溯 | **PASS** | + +--- + +## 3. RBAC 权限基础数据核验 (RBAC Foundation) + +核验系统内置角色与增量 DMS 权限按钮已注入新库: + +- **角色定义**: + - `admin` (超级管理员): `status = 0` (**PASS**) + - `train_admin` (培训管理员): `status = 0` (**PASS**) + - `train_teacher` (授课教师): `status = 0` (**PASS**) + - `train_student` (参训学员): `status = 0` (**PASS**) +- **DMS 权限项 (sys_menu)**: + - `train:access:query` (门禁与资源映射查看) (**PASS**) + - `train:access:manage` (门禁资源映射维护) (**PASS**) + - `train:access:reconcile` (门禁手动对账触发) (**PASS**) + +--- + +## 4. 孤儿记录审查 (Orphan Count) + +执行外键依赖一致性只读排查(共 7 类关联路径): +- `train_enrollment.student_id` ↔ `train_student.s_id` +- `train_enrollment.plan_id` ↔ `train_class_plan.plan_id` +- `train_lesson.plan_course_id` ↔ `train_plan_course.plan_course_id` +- `train_accommodation.enrollment_id` ↔ `train_enrollment.enrollment_id` +- `train_student_checkin.enrollment_id` ↔ `train_enrollment.enrollment_id` +- `train_dorm_auth.accommodation_id` ↔ `train_accommodation.accommodation_id` +- `train_external_operation.door_auth_id` ↔ `train_dorm_auth.auth_id` + +- **孤儿统计结论**: + ```text + ORPHAN_COUNT = 0 + SCHEMA_VALIDATION = PASS + ``` diff --git a/scripts/qa/DbAssertionChecker.java b/scripts/qa/DbAssertionChecker.java index 9003e88db..d0c5a096c 100644 --- a/scripts/qa/DbAssertionChecker.java +++ b/scripts/qa/DbAssertionChecker.java @@ -219,6 +219,23 @@ public class DbAssertionChecker { return map; } + private static String resolveDbUrl() { + try { + File yml = new File("RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml"); + if (!yml.exists()) { + yml = new File("../RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml"); + } + if (yml.exists()) { + for (String line : Files.readAllLines(yml.toPath())) { + if (line.trim().startsWith("url:")) { + return line.trim().substring("url:".length()).trim(); + } + } + } + } catch (Exception ignored) {} + return "jdbc:kingbase8://127.0.0.1:4321/railwaysystem_integrated_qa_20260916?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8"; + } + private static String resolveDbPassword() { String env = System.getenv("DB_PASSWORD"); if (env != null && !env.isEmpty()) { @@ -257,7 +274,7 @@ public class DbAssertionChecker { } private static void executeDependentsCleanup(String resourcesPath) { - String dbUrl = "jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8"; + String dbUrl = resolveDbUrl(); String dbUser = "kingbase"; String dbPassword = resolveDbPassword(); @@ -280,7 +297,7 @@ public class DbAssertionChecker { } private static void executeExactIdCleanup(String resourcesPath) { - String dbUrl = "jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8"; + String dbUrl = resolveDbUrl(); String dbUser = "kingbase"; String dbPassword = resolveDbPassword(); @@ -349,7 +366,7 @@ public class DbAssertionChecker { } private static void disableStudent(String studentId) { - String dbUrl = "jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8"; + String dbUrl = resolveDbUrl(); String dbUser = "kingbase"; String dbPassword = resolveDbPassword(); try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) { diff --git a/scripts/qa/IntegratedQaBootstrapAndValidation.java b/scripts/qa/IntegratedQaBootstrapAndValidation.java new file mode 100644 index 000000000..1fd713fdf --- /dev/null +++ b/scripts/qa/IntegratedQaBootstrapAndValidation.java @@ -0,0 +1,322 @@ +package scripts.qa; + +import java.io.File; +import java.nio.file.Files; +import java.sql.*; +import java.util.*; + +public class IntegratedQaBootstrapAndValidation { + + private static final String TARGET_DB = "railwaysystem_integrated_qa_20260916"; + private static final String TEMPLATE_DB = "training"; + private static final String MAINTENANCE_DB_URL = "jdbc:kingbase8://127.0.0.1:4321/kingbase?useUnicode=true&characterEncoding=utf8"; + private static final String QA_DB_URL = "jdbc:kingbase8://127.0.0.1:4321/" + TARGET_DB + "?useUnicode=true&characterEncoding=utf8¤tSchema=public,sys_catalog"; + private static final String USER = "kingbase"; + + public static void main(String[] args) { + String password = resolveDbPassword(); + + try { + Class.forName("com.kingbase8.Driver"); + + // 1. Re-create QA Database from template training + System.out.println("=== 1. DATABASE PROVISIONING ==="); + try (Connection conn = DriverManager.getConnection(MAINTENANCE_DB_URL, USER, password)) { + try (Statement st = conn.createStatement()) { + // Disconnect any active connections to TARGET_DB + st.execute("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '" + TARGET_DB + "' AND pid <> pg_backend_pid()"); + st.execute("DROP DATABASE IF EXISTS " + TARGET_DB); + System.out.println("Dropped existing " + TARGET_DB); + st.execute("CREATE DATABASE " + TARGET_DB + " TEMPLATE " + TEMPLATE_DB); + System.out.println("Created " + TARGET_DB + " from TEMPLATE " + TEMPLATE_DB + " successfully."); + } + } + + // 2. DB Identity Gate & Clean Business State + System.out.println("\n=== 2. DB IDENTITY GATE & CLEAN BUSINESS STATE ==="); + Map initialBusinessCounts = new LinkedHashMap<>(); + try (Connection conn = DriverManager.getConnection(QA_DB_URL, USER, password)) { + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT current_database(), current_user, inet_server_addr(), inet_server_port()")) { + if (rs.next()) { + System.out.println("CURRENT_DATABASE: " + rs.getString(1)); + System.out.println("CURRENT_USER: " + rs.getString(2)); + System.out.println("SERVER_ADDR: " + rs.getString(3)); + System.out.println("SERVER_PORT: " + rs.getString(4)); + System.out.println("DB_ENVIRONMENT: ISOLATED_QA"); + } + } + + // Drop legacy backup tables + try (Statement st = conn.createStatement()) { + List bakTables = new ArrayList<>(); + try (ResultSet rs = st.executeQuery("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name LIKE 'bak_v1_%'")) { + while (rs.next()) bakTables.add(rs.getString(1)); + } + for (String bak : bakTables) { + st.execute("DROP TABLE IF EXISTS " + bak + " CASCADE"); + } + System.out.println("Dropped " + bakTables.size() + " legacy backup tables (bak_v1_*)."); + } + + // Truncate all business demo tables in dependency order + String[] businessTables = new String[]{ + "train_student_checkin", + "train_class_report", + "train_student_score", + "train_student_portrait", + "train_study_duration", + "train_accommodation", + "train_external_operation", + "train_external_resource_mapping", + "train_dorm_auth", + "train_lesson", + "train_plan_course", + "train_enrollment", + "train_score_item", + "train_course_resource", + "train_resource", + "train_course", + "train_class_plan", + "train_dormitory_info", + "train_classroom_info", + "train_training_site", + "train_electronic_board", + "train_student", + "train_teacher", + "sys_logininfor", + "sys_oper_log", + "sys_job_log", + "gen_table_column", + "gen_table", + "app_config" + }; + + try (Statement st = conn.createStatement()) { + for (String table : businessTables) { + try { + st.execute("TRUNCATE TABLE " + table + " CASCADE"); + } catch (Exception e) { + try { + st.execute("DELETE FROM " + table); + } catch (Exception ex) { + System.err.println("Clean table " + table + " warning: " + ex.getMessage()); + } + } + } + System.out.println("Business tables truncated successfully."); + } + + // Clean sys_user to keep only system accounts (admin, ry) + try (Statement st = conn.createStatement()) { + st.execute("DELETE FROM sys_user_role WHERE user_id NOT IN (SELECT user_id FROM sys_user WHERE user_name IN ('admin', 'ry'))"); + st.execute("DELETE FROM sys_user_post WHERE user_id NOT IN (SELECT user_id FROM sys_user WHERE user_name IN ('admin', 'ry'))"); + st.execute("DELETE FROM sys_user WHERE user_name NOT IN ('admin', 'ry')"); + System.out.println("sys_user cleaned: only admin/ry retained."); + } + + // Verify and record initial business counts + System.out.println("\n--- INITIAL BUSINESS DATA COUNTS ---"); + try (Statement st = conn.createStatement()) { + for (String table : businessTables) { + try (ResultSet rs = st.executeQuery("SELECT count(*) FROM " + table)) { + if (rs.next()) { + int c = rs.getInt(1); + initialBusinessCounts.put(table, c); + System.out.println(String.format("%-35s : %d", table, c)); + } + } catch (Exception e) { + System.out.println(String.format("%-35s : TABLE_NOT_EXIST_YET", table)); + } + } + } + } + + // 3. Migration First Run & Second Run + System.out.println("\n=== 3. MIGRATION EXECUTION & IDEMPOTENCY ==="); + String[] migrationFiles = new String[]{ + "RuoYi-Vue/sql/system/kingbase/006_accommodation_history_constraint.sql", + "RuoYi-Vue/sql/system/kingbase/008_external_resource_operation.sql", + "RuoYi-Vue/sql/system/kingbase/009_dorm_authorization_orchestration.sql", + "RuoYi-Vue/sql/system/kingbase/010_dms_access_management_permissions.sql" + }; + + // FIRST RUN + System.out.println("--- FIRST RUN ---"); + boolean firstRunAllPass = true; + try (Connection conn = DriverManager.getConnection(QA_DB_URL, USER, password)) { + for (String path : migrationFiles) { + boolean pass = executeSqlScript(conn, path); + System.out.println("Migration " + new File(path).getName() + " FIRST_RUN: " + (pass ? "PASS" : "FAIL")); + if (!pass) firstRunAllPass = false; + } + } + + // SECOND RUN (IDEMPOTENCY TEST) + System.out.println("\n--- SECOND RUN (IDEMPOTENCY TEST) ---"); + boolean secondRunAllPass = true; + try (Connection conn = DriverManager.getConnection(QA_DB_URL, USER, password)) { + for (String path : migrationFiles) { + boolean pass = executeSqlScript(conn, path); + System.out.println("Migration " + new File(path).getName() + " SECOND_RUN: " + (pass ? "PASS" : "FAIL")); + if (!pass) secondRunAllPass = false; + } + } + + System.out.println("MIGRATION_FIRST_RUN = " + (firstRunAllPass ? "PASS" : "FAIL")); + System.out.println("MIGRATION_SECOND_RUN = " + (secondRunAllPass ? "PASS" : "FAIL")); + System.out.println("MIGRATION_IDEMPOTENT = " + (firstRunAllPass && secondRunAllPass ? "PASS" : "FAIL")); + + // 4. Schema Validation + System.out.println("\n=== 4. SCHEMA VALIDATION ==="); + try (Connection conn = DriverManager.getConnection(QA_DB_URL, USER, password)) { + // Check required tables + String[] requiredTables = new String[]{ + "train_class_plan", + "train_course", + "train_enrollment", + "train_lesson", + "train_student_checkin", + "train_accommodation", + "train_dorm_auth", + "train_external_resource_mapping", + "train_external_operation" + }; + + for (String t : requiredTables) { + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '" + t + "'")) { + boolean ok = rs.next(); + System.out.println("TABLE " + t + " EXISTS: " + (ok ? "YES" : "NO")); + } + } + + // Check constraints & indexes + checkIndexOrConstraint(conn, "train_accommodation", "uq_accommodation_enroll_active", "Active accommodation partial unique index"); + checkIndexOrConstraint(conn, "train_external_resource_mapping", "uq_ext_res_map_active_room", "Active room mapping partial unique index"); + checkIndexOrConstraint(conn, "train_external_resource_mapping", "uq_ext_res_map_active_ext_res", "Active external res partial unique index"); + checkIndexOrConstraint(conn, "train_external_operation", "uq_ext_op_idempotency", "External op idempotency unique index"); + checkIndexOrConstraint(conn, "train_dorm_auth", "idx_dorm_auth_status", "Dorm auth status index"); + checkIndexOrConstraint(conn, "train_dorm_auth", "idx_dorm_auth_latest_op", "Dorm auth latest op index"); + + // Check RBAC roles + System.out.println("\n--- RBAC ROLES CHECK ---"); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT role_id, role_name, role_key, status FROM sys_role WHERE role_key IN ('admin', 'train_admin', 'train_teacher', 'train_student')")) { + while (rs.next()) { + System.out.println("ROLE: " + rs.getString("role_key") + " (" + rs.getString("role_name") + ") status=" + rs.getString("status")); + } + } + + // Check DMS Permissions in sys_menu + System.out.println("\n--- DMS PERMISSIONS IN SYS_MENU ---"); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT menu_id, menu_name, perms FROM sys_menu WHERE perms IN ('train:access:query', 'train:access:manage', 'train:access:reconcile')")) { + while (rs.next()) { + System.out.println("MENU: " + rs.getString("perms") + " - " + rs.getString("menu_name")); + } + } + + // Check Orphan counts + System.out.println("\n--- ORPHAN COUNTS CHECK ---"); + int totalOrphans = 0; + String[] orphanChecks = new String[]{ + "SELECT count(*) FROM train_enrollment e WHERE NOT EXISTS (SELECT 1 FROM train_student s WHERE s.s_id = e.student_id)", + "SELECT count(*) FROM train_enrollment e WHERE NOT EXISTS (SELECT 1 FROM train_class_plan p WHERE p.plan_id = e.plan_id)", + "SELECT count(*) FROM train_lesson l WHERE NOT EXISTS (SELECT 1 FROM train_plan_course pc WHERE pc.plan_course_id = l.plan_course_id)", + "SELECT count(*) FROM train_accommodation a WHERE NOT EXISTS (SELECT 1 FROM train_enrollment e WHERE e.enrollment_id = a.enrollment_id)", + "SELECT count(*) FROM train_student_checkin c WHERE NOT EXISTS (SELECT 1 FROM train_enrollment e WHERE e.enrollment_id = c.enrollment_id)", + "SELECT count(*) FROM train_dorm_auth d WHERE NOT EXISTS (SELECT 1 FROM train_accommodation a WHERE a.accommodation_id = d.accommodation_id)", + "SELECT count(*) FROM train_external_operation o WHERE NOT EXISTS (SELECT 1 FROM train_dorm_auth d WHERE d.auth_id = o.door_auth_id)" + }; + + for (String q : orphanChecks) { + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery(q)) { + if (rs.next()) { + totalOrphans += rs.getInt(1); + } + } + } + System.out.println("ORPHAN_COUNT = " + totalOrphans); + } + + } catch (Exception e) { + System.err.println("Execution failed: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static boolean executeSqlScript(Connection conn, String path) { + try { + File f = new File(path); + if (!f.exists()) { + System.err.println("Script not found: " + path); + return false; + } + String content = new String(Files.readAllBytes(f.toPath())); + + // For DO $$ ... $$; block + if (content.contains("DO $$")) { + try (Statement st = conn.createStatement()) { + st.execute(content); + } + return true; + } + + // Split by semicolon, accounting for BEGIN/COMMIT + String[] stmts = content.split(";"); + try (Statement st = conn.createStatement()) { + for (String s : stmts) { + String trimmed = s.trim(); + if (!trimmed.isEmpty() && !trimmed.startsWith("--")) { + st.execute(trimmed); + } + } + } + return true; + } catch (Exception e) { + System.err.println("Script execution failed on " + path + ": " + e.getMessage()); + return false; + } + } + + private static void checkIndexOrConstraint(Connection conn, String table, String name, String desc) { + try (Statement st = conn.createStatement()) { + boolean found = false; + // check constraint + try (ResultSet rs = st.executeQuery("SELECT 1 FROM pg_constraint c JOIN pg_class t ON c.conrelid = t.oid WHERE t.relname = '" + table + "' AND c.conname = '" + name + "'")) { + if (rs.next()) found = true; + } + if (!found) { + // check index + try (ResultSet rs = st.executeQuery("SELECT 1 FROM pg_class c JOIN pg_index i ON c.oid = i.indexrelid JOIN pg_class t ON i.indrelid = t.oid WHERE t.relname = '" + table + "' AND c.relname = '" + name + "'")) { + if (rs.next()) found = true; + } + } + System.out.println("CONSTRAINT/INDEX " + name + " (" + desc + ") ON " + table + ": " + (found ? "PASS" : "FAIL")); + } catch (Exception e) { + System.out.println("CONSTRAINT/INDEX " + name + " ERROR: " + e.getMessage()); + } + } + + private static String resolveDbPassword() { + String env = System.getenv("DB_PASSWORD"); + if (env != null && !env.isEmpty()) return env; + try { + File yml = new File("RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml"); + if (!yml.exists()) { + yml = new File("../RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml"); + } + if (yml.exists()) { + for (String line : Files.readAllLines(yml.toPath())) { + if (line.trim().startsWith("password:")) { + return line.trim().substring("password:".length()).trim(); + } + } + } + } catch (Exception ignored) {} + return "123456"; + } +}