合并门禁:将R3门禁能力集成至一期综合验收
- 将QA数据库配置对齐至隔离库 railwaysystem_integrated_qa_20260916
This commit is contained in:
1 parent
307c9cd172
commit
63f463d094
11 files changed
+714
-4
No files matched your search
@@ -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"]
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
return this.$store.state.settings.sideTheme
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -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 最终结论与后续工作规划
|
||||
+56
@@ -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`
|
||||
+75
@@ -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` |
|
||||
@@ -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 <branch>`
|
||||
- **校验结果矩阵**:
|
||||
|
||||
| 核查分支 | 是否为祖先 (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`)
|
||||
+36
@@ -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`
|
||||
+44
@@ -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 数据库中,以标准业务逻辑与受控脚本构造核心业务验收场景数据(包含培训班、课程、学员、排课、报到、住宿、门禁授权编排等最小闭环数据);
|
||||
- 跑通后端核心业务流与状态机闭环对账。
|
||||
+70
@@ -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
|
||||
```
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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<String, Integer> 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<String> 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";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user