diff --git a/docs/database-v2/00_readme.md b/docs/database-v2/00_readme.md new file mode 100644 index 000000000..ce1e6f0c1 --- /dev/null +++ b/docs/database-v2/00_readme.md @@ -0,0 +1,78 @@ +# RailwaySystem 数据库 V2 DDL 生成与设计审查概览 (Round 2 最终静态收尾版) + +> **设计状态**:数据库 V2 物理 DDL 最终静态收尾就绪(已完成 Round 2 全部 7 项缺陷修复,达到“允许进入测试迁移”标准) +> **最高依据**:[`RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/doc/RailwaySystem%20数据库%20V2%20字段设计与%20Kingbase%20DDL%20规范%20V1.0.md) +> **业务背景参考**:[`RailwaySystem 智慧培训系统一期 PRD V1.0.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/doc/RailwaySystem%20智慧培训系统一期%20PRD%20V1.0.md) +> **技术基线**:`main@4b450f46`,KingbaseES V009R003C018 (`database=training`, `schema=public`, `database_mode=mysql`) +> **操作定位**:**纯静态收尾与审查阶段**(严禁执行 SQL、严禁修改真实库、严禁改动业务代码、严禁自动 git commit)。 + +--- + +## 1. 阶段目标与执行边界 + +本阶段严格依据技术负责人 Final Static Review 指示,修复 DDL 中剩余的 7 个问题(R2-01 ~ R2-07),使数据库 V2 DDL 达到“允许进入测试迁移”的标准。 + +### 核心执行边界 +- [x] **21 张核心业务表模型冻结**:8 新增 + 13 保留/重构 + 淘汰 `train_score_info` + 严禁 `train_dorm_bed`; +- [x] **删除业务选择型默认值**:核验结果、考勤方式、场地类型、住宿需求、来源类型等必须由应用层明确提供; +- [x] **解耦两个同名 `source_type`**:区分学员主档进入来源与名单进入来源; +- [x] **删除正式迁移对象的 `IF NOT EXISTS`**:避免因已有错误对象导致 DDL 静默跳过; +- [x] **建立 DDL 事务能力探针 (Gate A)**:实证验证实例是否支持 DDL 回滚; +- [x] **骨架校验升级**:Preflight (Gate B) 采用集合比对,Verify 采用定义级深度验收; +- [x] **架构决策冻结**:固化排课/住宿并发控制与跨班一致性 Service 校验边界; +- [x] **绝对零写操作与零代码污染**:未向数据库执行写操作,未修改 Java/Vue 代码,未执行 git commit。 + +--- + +## 2. 成果物资产全景清单 + +### 2.1 物理 SQL 脚本清单 (`RuoYi-Vue/sql/train/v2/`) + +| 序号 / 关卡 | 脚本文件 | 事务边界 | 核心职责 | +| :---: | :--- | :---: | :--- | +| **Gate A** | [`000_tx_capability_probe.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_tx_capability_probe.sql) | 探针显式事务 | **执行前能力探针**:测试 DDL 事务回滚能力,若表残留强行阻断 | +| **Gate B** | [`000_preflight.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_preflight.sql) | 纯只读骨架断言 | **Phase 0.5 骨架强校验**:集合差集校验 14 PK + 24 FK + 7 UNIQUE + 6 Identity + 9 Triggers | +| **Stage 1** | [`001_backup_current_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/001_backup_current_schema.sql) | `BEGIN ... COMMIT` | 备份当前 14 张表数据快照;增加防覆盖强校验,禁止 DROP CASCADE | +| **Stage 2** | [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql) | `BEGIN ... COMMIT` | 创建 8 张 V2 新表(无 `IF NOT EXISTS`,住宿默认0预分配,无业务默认值) | +| **Stage 3** | [`003_alter_master_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql) | `BEGIN ... COMMIT` | 卸载旧触发器与外键,调整 8 张主表(严格 `ADD COLUMN`,加容量 CHECK) | +| **Stage 4** | [`004_rebuild_business_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql) | `BEGIN ... COMMIT` | 校验 0 行后重构 5 张表(删除 `verify_result`/`checkin_way` 业务默认值) | +| **Stage 5** | [`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql) | `BEGIN ... COMMIT` | 集中定义 13 组 UNIQUE、26 组 `ON DELETE NO ACTION` 外键与 9 条单行 CHECK | +| **Stage 6** | [`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql) | `BEGIN ... COMMIT` | 建立外键索引、排课复合冲突索引与双非空过滤的部分唯一索引(无 `IF NOT EXISTS`) | +| **-** | [`007_dicts.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/007_dicts.sql) | - | 声明 16 组业务字典注册脚本(**本结构迁移阶段跳过不执行**,留待权限阶段) | +| **Stage 7** | [`008_migrate_teacher.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/008_migrate_teacher.sql) | `BEGIN ... COMMIT` | 平滑迁移 1 条教师真实数据,校验完整后固化 `status`/`del_flag` 为 NOT NULL 并删除旧列 | +| **Stage 8** | [`009_drop_legacy_score.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/009_drop_legacy_score.sql) | `BEGIN ... COMMIT` | 前置 0 行强阻断校验后,显式解绑旧约束,常规 DROP 淘汰旧表 `train_score_info` | +| **Stage 9** | [`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) | 纯只读深度断言 | **“定义级深度验收”** 全量自动化强断言(逐表、列列表、外键删除规则、CHECK定义、默认值) | + +### 2.2 应急回滚资产清单 (`RuoYi-Vue/sql/train/v2/rollback/`) + +| 序号 | 回滚文件 | 核心职责 | +| :---: | :--- | :--- | +| **0** | [`000_phase05_exact_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/000_phase05_exact_schema.sql) | **Phase 0.5 真实物理基线 DDL**(14表字段、PK、24个FK、7组UNIQUE、9个触发器) | +| **1** | [`001_rollback_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/001_rollback_new_tables.sql) | 显式解除外键依赖,按拓扑反序常规 DROP 8 张 V2 新增表(`BEGIN ... COMMIT`) | +| **2** | [`002_rollback_modified_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/002_rollback_modified_tables.sql) | 基于 `000_phase05_exact_schema.sql` 精确结构重建 13 张表,灌回 `bak_v1_*` 数据并核验教师行数 | +| **3** | [`003_restore_legacy_score.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/003_restore_legacy_score.sql) | 基于精确 DDL 重建历史 `train_score_info` 表及 3 个外键与唯一约束 | +| **4** | [`README.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/README.md) | 应急回滚指南(解耦结构与数据,明确依赖 `000` 结构与 `bak_v1_*` 数据的执行规范) | + +### 2.3 架构与设计审查技术文档 (`docs/database-v2/`) + +| 序号 | 文档名称 | 主要内容 | +| :---: | :--- | :--- | +| **1** | [`00_readme.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/00_readme.md) | 本阶段全景综述、规范基线与资产索引导航 | +| **2** | [`01_table_inventory.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/01_table_inventory.md) | 21 张正式业务表清单、新旧对比、三大核心 ID 落地映射、字段默认值修正 | +| **3** | [`02_migration_plan.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/02_migration_plan.md) | 迁移执行流水线、双门禁 (Gate A/B) 准入机制、单文件显式事务边界与断点恢复 | +| **4** | [`03_constraint_index_review.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/03_constraint_index_review.md) | PK、26 组 NO ACTION 外键、13 组 UNIQUE、9 条 CHECK 与复合索引全面审查 | +| **5** | [`04_risk_review.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/04_risk_review.md) | **R-01 至 R-17** 全景风险深度剖析(固化架构决策 A/B,解耦双 source_type) | +| **6** | [`05_execution_contract.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/05_execution_contract.md) | **执行契约**(Gate A 事务探针 + Gate B 精确骨架 + 显式事务与即刻熔断) | +| **7** | [`06_review_round1_changes.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/06_review_round1_changes.md) | **Round 1 修订闭环记录表**(17 项评审意见落实明细) | +| **8** | [`07_review_round2_changes.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/07_review_round2_changes.md) | **Round 2 修订闭环记录表**(R2-01 ~ R2-07 最终静态收尾明细) | + +--- + +## 3. 人工 Review 准入与优先建议 + +建议高级技术负责人优先 Review 以下关键文件: +1. **优先审查 Round 2 修订记录表**:[`docs/database-v2/07_review_round2_changes.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/07_review_round2_changes.md)(核验 R2-01 ~ R2-07 修复明细); +2. **优先审查执行契约与探针**:[`docs/database-v2/05_execution_contract.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/05_execution_contract.md) 与 [`RuoYi-Vue/sql/train/v2/000_tx_capability_probe.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_tx_capability_probe.sql)(核验 Gate A 事务原子性实证机制); +3. **优先审查 Preflight 骨架校验**:[`RuoYi-Vue/sql/train/v2/000_preflight.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_preflight.sql)(核验集合比对 14 PK + 24 FK + 7 UNIQUE + 6 Identity + 9 Triggers); +4. **优先审查 Verify 定义级验收**:[`RuoYi-Vue/sql/train/v2/010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql)(核验定义级断言、CHECK 逻辑与默认值强断言); +5. **优先审查核心建表与重构脚本**:[`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql) 与 [`004_rebuild_business_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql)(核验无 `IF NOT EXISTS`、无业务默认值及住宿状态 0)。 diff --git a/docs/database-v2/01_table_inventory.md b/docs/database-v2/01_table_inventory.md new file mode 100644 index 000000000..1121375da --- /dev/null +++ b/docs/database-v2/01_table_inventory.md @@ -0,0 +1,199 @@ +# RailwaySystem 数据库 V2 表结构清单与模型比对 (Round 1 修订版) + +> **技术依据**:[`RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/doc/RailwaySystem%20数据库%20V2%20字段设计与%20Kingbase%20DDL%20规范%20V1.0.md) +> **状态**:一期 21 张培训业务表全覆盖冻结清单(已完成 Round 1 字段与约束精确审查修订)。 + +--- + +## 1. 21 张正式表全景分类 + +```mermaid +mindmap + root((RailwaySystem V2 业务表)) + 基础资料表 (9张) + train_teacher (教师主档) + train_student (学员长期主档) + train_course (课程基础主档) + train_resource (新增: 统一教学资源) + train_course_resource (新增: 课程资源关联) + train_classroom_info (教室信息) + train_training_site (实训场地) + train_electronic_board (电子班牌设备) + train_dormitory_info (宿舍房间) + 培训组织表 (4张) + train_class_plan (培训班主档) + train_plan_course (新增: 培训班课程实例) + train_enrollment (新增: 学员参训档案) + train_lesson (新增: 教学活动课次) + 培训执行表 (4张) + train_accommodation (新增: 学员住宿安排) + train_dorm_auth (门禁授权同步) + train_class_report (现场报到记录) + train_student_checkin (课次考勤打卡) + 培训结果表 (4张) + train_score_item (新增: 考核成绩项配置) + train_student_score (新增: 学员单项成绩明细) + train_study_duration (自主学习时长记录) + train_student_portrait (学员全期画像评估) + 淘汰表 (1张) + train_score_info (彻底下线) +``` + +--- + +## 2. 三大核心 ID 架构映射 + +V2 架构通过三个领域核心 ID 彻底取代旧模型中跨多表重复冗余的复合外键串(`student_id + plan_id + c_id + lesson_time`): + +| 核心 ID 标识 | 业务实体表 | 业务语义 | 衍生与引用的业务表 | +| :--- | :--- | :--- | :--- | +| **`enrollment_id`** | [`train_enrollment`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql#L72) | **某学员参加某一期培训班的参训全生命周期档案** | `train_accommodation`
`train_class_report`
`train_student_checkin`
`train_student_score`
`train_study_duration`
`train_student_portrait` | +| **`plan_course_id`** | [`train_plan_course`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql#L52) | **某一期培训班中包含的某一门具体课程实例** | `train_lesson`
`train_score_item`
`train_study_duration` | +| **`lesson_id`** | [`train_lesson`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql#L106) | **本期某门课程中的具体一次教学活动(课表课次)** | `train_student_checkin` | + +--- + +## 3. 21 张正式表详细结构与新旧比对 (含 Round 1 修订) + +### 3.1 基础资料模块 (9 张) + +#### 1. `train_teacher` (保留并结构升级) +- **主键**:`t_id` (varchar(32)) +- **数据保留**:保留当前生产环境唯一 1 行数据。 +- **字段变更**: + - 新增:`user_id` (bigint), `org_name` (varchar(100)), `job_title` (varchar(50)), `status` (smallint NOT NULL), `create_by` (varchar(64)), `update_by` (varchar(64)), `del_flag` (char(1) NOT NULL); + - 调整列宽:`t_name` varchar(50), `t_email` varchar(100), `remark` varchar(500); + - 数据迁移:由 `008_migrate_teacher.sql` 将 `t_department` -> `org_name`, `t_title` -> `job_title`, `create_user` -> `create_by`,并在校验后将 `status` 与 `del_flag` 固化为 `NOT NULL`; + - 淘汰删除:`t_department`, `t_title`, `create_user`。 + +#### 2. `train_student` (保留并重构为企业培训主档) +- **主键**:`s_id` (varchar(32)) +- **字段变更**: + - 彻底淘汰高校概念旧列:删除 `s_department`, `s_major`, `s_class`, `create_user`; + - 新增:`user_id` (bigint), `org_name` (varchar(100)), `s_email` (varchar(100)), `id_card_ciphertext` (text, 密文), `id_card_hash` (varchar(128), 精确防重哈希), `id_card_last4` (varchar(4), 脱敏展示), `face_ref` (varchar(255), 人脸凭据), `source_type` (smallint NOT NULL, 1管理员录入/2Excel导入/3平台同步), `status` (smallint NOT NULL), `create_by`, `update_by`, `del_flag`; + - 约束:`UNIQUE(id_card_hash)` (允许为 NULL)。 + +#### 3. `train_course` (保留并解耦资源附件) +- **主键**:`c_id` (varchar(32)) +- **字段变更**: + - 彻底淘汰集中附件旧列:删除 `attach_doc`, `attach_video`, `attach_model_link`, `model_resource_ids`, `create_user`; + - 新增:`c_code` (varchar(50), 统一编码), `status` (smallint NOT NULL), `create_by`, `update_by`, `del_flag`; + - 约束:`UNIQUE(c_code)`。 + +#### 4. `train_resource` (V2 全新建表) +- **主键**:`resource_id` (varchar(32)) +- **字段**:`resource_id`, `resource_name`, `resource_type` (1文档/2视频/3 3D模型/4 VR资源/5外部链接/6其他), `resource_url`, `description`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `del_flag`。 + +#### 5. `train_course_resource` (V2 全新建表) +- **主键**:`id` (bigint identity) +- **字段**:`id`, `c_id`, `resource_id`, `sort_no`, `create_by`, `create_time`。 +- **约束**:`UNIQUE(c_id, resource_id)`, 外键指向 `train_course` 与 `train_resource`。 + +#### 6. `train_classroom_info` (保留并补齐审计) +- **主键**:`class_room_id` (varchar(32)) +- **字段变更**:删除 `create_user`;补齐 `create_by`, `update_by`, `del_flag`;设置 `room_status` NOT NULL。 + +#### 7. `train_training_site` (保留并补齐审计) +- **主键**:`site_id` (varchar(32)) +- **字段变更**:删除 `create_user`;补齐 `create_by`, `update_by`, `del_flag`;设置 `site_status` NOT NULL。 + +#### 8. `train_electronic_board` (保留并升级交互架构) +- **主键**:`board_id` (varchar(32)) +- **字段变更**: + - 解绑旧关系:删除 `bind_plan_id`, `board_content`, `board_playmode`, `create_user`; + - 新增:`board_name` (varchar(100) NOT NULL, 必填无默认空串), `api_endpoint` (varchar(255)), `create_by`, `update_by`, `del_flag`; + - 约束:`UNIQUE(board_sn)`。 + +#### 9. `train_dormitory_info` (保留并优化房态模型) +- **主键**:`dorm_id` (varchar(32)) +- **字段变更**: + - 移除旧计数列:删除 `bed_total`, `bed_used`; + - 新增:`dorm_floor` (varchar(20) NOT NULL, 无猜测默认值), `capacity` (integer NOT NULL, 无猜测默认值), `gender_type` (smallint, 0不限/1男/2女), `create_by`, `update_by`, `del_flag`; + - 约束:`UNIQUE(dorm_building, dorm_room)`, `CHECK (capacity > 0)`。 + - **特别声明**:严禁增加 `train_dorm_bed` 表。 + +--- + +### 3.2 培训组织模块 (4 张) + +#### 10. `train_class_plan` (保留并重构为一班多课) +- **主键**:`plan_id` (varchar(32)) +- **字段变更**:删除旧单课 `c_id` 与旧教师 `t_id` 及 `create_user`;新增 `report_deadline`, `create_by`, `update_by`, `del_flag`。 + +#### 11. `train_plan_course` (V2 全新建表) +- **主键**:`plan_course_id` (bigint identity) +- **字段**:`plan_course_id`, `plan_id`, `c_id`, `sort_no`, `create_by`, `create_time`, `remark`。 +- **约束**:`UNIQUE(plan_id, c_id)`。 + +#### 12. `train_enrollment` (V2 全新建表 - 系统最核心关系) +- **主键**:`enrollment_id` (bigint identity) +- **字段**:`enrollment_id`, `plan_id`, `student_id`, `source_type` (smallint NOT NULL, 参训名单来源,由 train_enrollment_source 字典定义,无默认值), `enrollment_status` (DEFAULT 0 待报到), `accommodation_required` (smallint NOT NULL, 无默认值), `join_time`, `complete_time`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`UNIQUE(plan_id, student_id)`。 +- **注意**:此处 `source_type` 表示学员进入本期培训班名单的来源,与 `train_student.source_type`(长期人员主档来源)语义完全不同,严禁混用。 + +#### 13. `train_lesson` (V2 全新建表) +- **主键**:`lesson_id` (bigint identity) +- **字段**:`lesson_id`, `plan_course_id`, `t_id` (varchar(32) NOT NULL, 课次必要组成), `start_time`, `end_time`, `venue_type` (smallint NOT NULL, 1普通教室/2实训场地,无默认值), `class_room_id`, `site_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**: + - `CHECK (start_time < end_time)`; + - `CHECK ((venue_type = 1 AND class_room_id IS NOT NULL AND site_id IS NULL) OR (venue_type = 2 AND site_id IS NOT NULL AND class_room_id IS NULL))` (场地单行互斥)。 + +--- + +### 3.3 培训执行模块 (4 张) + +#### 14. `train_accommodation` (V2 全新建表) +- **主键**:`accommodation_id` (bigint identity) +- **字段**:`accommodation_id`, `enrollment_id`, `dorm_id`, `bed_no`, `assigned_time`, `accommodation_status` (smallint NOT NULL DEFAULT 0, 0预分配/1已入住/2已退宿), `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`UNIQUE(enrollment_id)`。 + +#### 15. `train_dorm_auth` (重构重建) +- **主键**:`auth_id` (bigint identity) +- **字段**:`auth_id`, `accommodation_id`, `auth_start`, `auth_end`, `face_sync_status` (DEFAULT 0 待下发), `retry_count` (DEFAULT 0), `last_error`, `external_auth_id`, `last_sync_time`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`UNIQUE(accommodation_id)`。 + +#### 16. `train_class_report` (重构重建) +- **主键**:`report_id` (bigint identity) +- **字段**:`report_id`, `enrollment_id`, `report_way` (smallint NOT NULL), `report_device_sn`, `report_time`, `verify_result` (smallint NOT NULL, 1通过/2不匹配/3人工放行,无默认值), `verify_note`, `create_by`, `create_time`, `remark`。 +- **约束**:`UNIQUE(enrollment_id)`。 + +#### 17. `train_student_checkin` (重构重建) +- **主键**:`checkin_id` (bigint identity) +- **字段**:`checkin_id`, `enrollment_id`, `lesson_id`, `board_id`, `checkin_way` (smallint NOT NULL, 1人脸/2刷卡/3扫码/4代签,无默认值), `sign_in_time`, `sign_out_time`, `attendance_status` (DEFAULT 0 缺勤), `manual_flag` (DEFAULT 0 自动), `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`UNIQUE(enrollment_id, lesson_id)`。 + +--- + +### 3.4 培训结果模块 (4 张) + +#### 18. `train_score_item` (V2 全新建表) +- **主键**:`score_item_id` (bigint identity) +- **字段**:`score_item_id`, `plan_id`, `plan_course_id`, `item_name`, `item_type`, `weight`, `full_score`, `sort_no`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`CHECK (weight >= 0.00 AND weight <= 100.00)`, `CHECK (full_score > 0.00)`。 + +#### 19. `train_student_score` (V2 全新建表) +- **主键**:`score_id` (bigint identity) +- **字段**:`score_id`, `enrollment_id`, `score_item_id`, `score` (numeric(7,2) NOT NULL, 无默认0), `source_type`, `source_system`, `source_event_id`, `import_time`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`UNIQUE(enrollment_id, score_item_id)`, `CHECK (score >= 0.00)`。 + +#### 20. `train_study_duration` (重构重建) +- **主键**:`dur_id` (bigint identity) +- **字段**:`dur_id`, `enrollment_id`, `plan_course_id`, `resource_id`, `learning_content`, `source_system`, `source_event_id`, `start_time`, `end_time`, `study_seconds`, `sync_time`, `create_by`, `create_time`, `remark`。 +- **约束**: + - `CHECK (study_seconds >= 0)`; + - `CHECK (end_time IS NULL OR start_time IS NULL OR end_time >= start_time)`; + - `CHECK (source_event_id IS NULL OR source_system IS NOT NULL)`; + - 双非空部分唯一索引:`UNIQUE(source_system, source_event_id) WHERE source_system IS NOT NULL AND source_event_id IS NOT NULL`。 + +#### 21. `train_student_portrait` (重构重建) +- **主键**:`portrait_id` (bigint identity) +- **字段**:`portrait_id`, `enrollment_id`, `attendance_rate`, `theory_score`, `practical_score`, `total_study_hour`, `label_tag`, `portrait_content`, `generated_time`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`。 +- **约束**:`UNIQUE(enrollment_id)`。 + +--- + +## 4. 淘汰旧表说明 + +### `train_score_info` +- **处理方式**:彻底下线并从 public 生产 schema 中 DROP; +- **现状保障**:实测当前行数精确为 0,无任何真实数据迁移风险。 diff --git a/docs/database-v2/02_migration_plan.md b/docs/database-v2/02_migration_plan.md new file mode 100644 index 000000000..dc6238af5 --- /dev/null +++ b/docs/database-v2/02_migration_plan.md @@ -0,0 +1,84 @@ +# RailwaySystem 数据库 V2 迁移执行计划 (Round 1 修订版) + +> **执行基线**:KingbaseES V009R003C018 (`schema=public`) +> **设计依据**:[`RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/doc/RailwaySystem%20数据库%20V2%20字段设计与%20Kingbase%20DDL%20规范%20V1.0.md) 及 Round 1 审查要求 +> **前置条件**:当前正式库中 `train_teacher` 精确包含 1 行数据,其余 13 张现有表均为 0 行。 + +--- + +## 1. 迁移执行总体流水线 (含 Preflight 与单文件事务) + +整个迁移任务划分为 6 个严格阶段,每个主脚本均自包含显式 `BEGIN ... COMMIT` 事务边界: + +```mermaid +flowchart TD + S0[Stage 0: 000 只读基线预检 Preflight] -->|核验通过| S1[Stage 1: 001 数据快照与结构备份] + S1 -->|防覆盖校验通过| S2[Stage 2: 物理结构准备] + subgraph Stage 2: 物理结构准备 (各脚本独立事务) + S2_1[002_create_new_tables.sql] --> S2_2[003_alter_master_tables.sql] + S2_2 --> S2_3[004_rebuild_business_tables.sql] + end + S2 --> S3[Stage 3: 完整性约束与索引注入] + subgraph Stage 3: 完整性建立 (各脚本独立事务) + S3_1[005_constraints.sql] --> S3_2[006_indexes.sql] + end + S3 --> S4[Stage 4: 数据平滑迁移与旧表淘汰] + subgraph Stage 4: 数据平滑处理 (各脚本独立事务) + S4_1[008_migrate_teacher.sql] --> S4_2[009_drop_legacy_score.sql] + end + S4 --> S5[Stage 5: 010 全面自动化强断言验收] + S5 -->|验收全部 PASS| DONE((迁移阶段成功闭环)) + S5 -.->|任何断言 FAIL| ROLLBACK[触发 rollback 逆向流水线] +``` + +--- + +## 2. 阶段明细与操作规范 + +### Stage 0: 只读基线预检 Preflight +- **执行脚本**:[`000_preflight.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_preflight.sql) +- **目标**:纯只读排查当前环境确实精确处于 Phase 0.5 初始状态。 +- **核验准则**: + - 14 张旧表全部存在,8 张 V2 新表均不存在; + - `train_teacher` 精确为 1 行,其余 13 张旧表精确为 0 行; + - 旧外键 `fk_classplan_course`、旧唯一约束及 9 个旧触发器全部完备。 +- **阻断控制**:任何一项不符合,立即抛出异常中止,拒绝进入未知环境。 + +### Stage 1: 数据快照备份 +- **执行脚本**:[`001_backup_current_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/001_backup_current_schema.sql) +- **目标**:在显式事务中为当前 14 张培训旧表建立物理快照(`bak_v1_*`)。 +- **防覆盖控制**:若检测到既有 `bak_v1_*` 已存在,立即抛出异常强阻断,杜绝二次执行覆盖最初 V1 备份。 + +### Stage 2: 物理结构准备 +- **执行脚本**: + 1. [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql):新建 8 张 V2 表(`t_id` 必填,场地互斥 CHECK,分数非负 CHECK); + 2. [`003_alter_master_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql):卸载主数据旧触发器,显式解除旧外键,调整 8 张主数据表结构(移除猜测默认值,加 `capacity > 0` CHECK); + 3. [`004_rebuild_business_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql):校验 0 行后,显式解绑旧外键并常规 DROP,按照 `enrollment_id` / `lesson_id` 重建 5 张业务表(增加学时时间与单号系统匹配 CHECK)。 + +### Stage 3: 完整性约束与索引注入 +- **执行脚本**: + 1. [`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql):集中为 21 张表建立 13 组 UNIQUE 约束、26 组 `ON DELETE NO ACTION` 外键与全量单行 CHECK 约束; + 2. [`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql):创建外键索引、排课多维复合冲突索引及双非空部分唯一索引。 + +### Stage 4: 数据平滑迁移与旧表淘汰 +- **执行脚本**: + 1. [`008_migrate_teacher.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/008_migrate_teacher.sql):将教师历史数据映射至新列,校验数据完整性后将 `status` 与 `del_flag` 固化为 NOT NULL,安全删除旧列; + 2. [`009_drop_legacy_score.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/009_drop_legacy_score.sql):前置 0 行校验后,显式解除历史约束并安全常规 DROP。 + +### Stage 5: 全面自动化强断言验收 +- **执行脚本**:[`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) +- **目标**:实现“失败即失败”,逐表逐项强断言,出现任何偏差直接抛异常报错。 + +--- + +## 3. 业务字典脚本定位特别说明 + +[`007_dicts.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/007_dicts.sql) **在当前数据库结构迁移阶段明确跳过、不执行**。 +原因:该脚本向 `sys_dict_*` 系统表写入业务字典数据,属于 API 契约与权限设计范畴。当前阶段保持数据库结构与应用层配置解耦,待后续权限设计阶段统一执行。 + +--- + +## 4. 断点恢复与精确回滚机制 + +- **单脚本失败自动撤销**:由于各脚本包含显式 `BEGIN ... COMMIT`,单脚本内部任何错误会自动触发事务 ROLLBACK,数据库保持在上一个脚本执行成功的整齐状态; +- **精确全量回滚保障**:若需全量还原,严格基于 [`rollback/000_phase05_exact_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/000_phase05_exact_schema.sql) 的真实物理 DDL 重建结构,结合 `bak_v1_*` 快照灌回数据,彻底消除 CTAS 丢失结构缺陷。 diff --git a/docs/database-v2/03_constraint_index_review.md b/docs/database-v2/03_constraint_index_review.md new file mode 100644 index 000000000..64e18576c --- /dev/null +++ b/docs/database-v2/03_constraint_index_review.md @@ -0,0 +1,126 @@ +# RailwaySystem 数据库 V2 约束与索引技术审查报告 (Round 1 修订版) + +> **审查依据**:[`RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/doc/RailwaySystem%20数据库%20V2%20字段设计与%20Kingbase%20DDL%20规范%20V1.0.md) 及 Round 1 审查要求 +> **审查对象**:[`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql) 与 [`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql) + +--- + +## 1. 主键(PRIMARY KEY)审查 + +V2 模型包含 21 张正式业务表,主键设计清晰划分为两类: + +### 1.1 业务编码主键 (8 张基础表,varchar(32)) +- 适用表:`train_teacher` (t_id), `train_student` (s_id), `train_course` (c_id), `train_resource` (resource_id), `train_classroom_info` (class_room_id), `train_training_site` (site_id), `train_electronic_board` (board_id), `train_dormitory_info` (dorm_id), `train_class_plan` (plan_id)。 +- **审查结论**:保持既有若依与业务系统自定义编码惯例,支持业务前缀与分布式生成;在 Kingbase 中具备唯一 B-Tree 聚集索引。 + +### 1.2 数字自增主键 (13 张关系/过程表,bigint identity) +- 适用表:`train_course_resource` (id), `train_plan_course` (plan_course_id), `train_enrollment` (enrollment_id), `train_lesson` (lesson_id), `train_accommodation` (accommodation_id), `train_dorm_auth` (auth_id), `train_class_report` (report_id), `train_student_checkin` (checkin_id), `train_score_item` (score_item_id), `train_student_score` (score_id), `train_study_duration` (dur_id), `train_student_portrait` (portrait_id)。 +- **语法形式**:`BIGINT GENERATED BY DEFAULT AS IDENTITY`。 +- **审查结论**:严格杜绝 MySQL 方言 `AUTO_INCREMENT`,在 KingbaseES 下生成标准序列并被 JDBC 驱动识别。 + +--- + +## 2. 外键约束(FOREIGN KEY)审查 + +### 2.1 26 组业务外键全量拓扑 + +```text +train_course_resource (c_id) -> train_course (c_id) +train_course_resource (resource_id) -> train_resource (resource_id) + +train_electronic_board (bind_classroom_id) -> train_classroom_info (class_room_id) + +train_plan_course (plan_id) -> train_class_plan (plan_id) +train_plan_course (c_id) -> train_course (c_id) + +train_enrollment (plan_id) -> train_class_plan (plan_id) +train_enrollment (student_id) -> train_student (s_id) + +train_lesson (plan_course_id) -> train_plan_course (plan_course_id) +train_lesson (t_id) -> train_teacher (t_id) +train_lesson (class_room_id) -> train_classroom_info (class_room_id) +train_lesson (site_id) -> train_training_site (site_id) + +train_accommodation (enrollment_id) -> train_enrollment (enrollment_id) +train_accommodation (dorm_id) -> train_dormitory_info (dorm_id) + +train_dorm_auth (accommodation_id) -> train_accommodation (accommodation_id) + +train_class_report (enrollment_id) -> train_enrollment (enrollment_id) + +train_student_checkin (enrollment_id) -> train_enrollment (enrollment_id) +train_student_checkin (lesson_id) -> train_lesson (lesson_id) +train_student_checkin (board_id) -> train_electronic_board (board_id) + +train_score_item (plan_id) -> train_class_plan (plan_id) +train_score_item (plan_course_id) -> train_plan_course (plan_course_id) + +train_student_score (enrollment_id) -> train_enrollment (enrollment_id) +train_student_score (score_item_id) -> train_score_item (score_item_id) + +train_study_duration (enrollment_id) -> train_enrollment (enrollment_id) +train_study_duration (plan_course_id) -> train_plan_course (plan_course_id) +train_study_duration (resource_id) -> train_resource (resource_id) + +train_student_portrait (enrollment_id) -> train_enrollment (enrollment_id) +``` + +### 2.2 删除规则原则审查 +- **核心合规**:所有 26 组外键规则均为 `ON DELETE NO ACTION`,**零级联删除(CASCADE)**; +- **业务依据**:培训班作废、课程停用或学员离校,绝不可导致学员考勤、历史成绩、学时记录与住宿轨迹发生连带物理抹除,系统必须保留可追溯痕迹; +- **系统表解耦**:所有培训表未对 `sys_user` 建立物理外键约束,仅在业务代码层做逻辑关联,避免跨领域强耦合。 + +--- + +## 3. 唯一约束(UNIQUE)审查 + +V2 DDL 共注入 13 组核心业务 UNIQUE 约束: + +| 序号 | 约束名称 | 作用表 | 约束字段 | 业务防重目标 | +| :--- | :--- | :--- | :--- | :--- | +| 1 | `uq_student_idcard_hash` | `train_student` | `id_card_hash` | 学员身份证精确防重(允许 NULL) | +| 2 | `uq_course_code` | `train_course` | `c_code` | 课程业务编码全局唯一 | +| 3 | `uq_board_sn` | `train_electronic_board` | `board_sn` | 班牌设备硬件序列号全局唯一 | +| 4 | `uq_dorm_building_room` | `train_dormitory_info` | `(dorm_building, dorm_room)` | 楼栋与房间组合唯一 | +| 5 | `uq_course_resource` | `train_course_resource` | `(c_id, resource_id)` | 课程与资源绑定防重复 | +| 6 | `uq_plan_course` | `train_plan_course` | `(plan_id, c_id)` | 同一培训班不可重复配置相同课程 | +| 7 | `uq_enrollment` | `train_enrollment` | `(plan_id, student_id)` | 同一学员不可重复加入同一培训班 | +| 8 | `uq_accommodation` | `train_accommodation` | `(enrollment_id)` | 一次参训单期唯一有效住宿分配 | +| 9 | `uq_dorm_auth` | `train_dorm_auth` | `(accommodation_id)` | 一次住宿安排对应唯一门禁同步记录 | +| 10 | `uq_class_report` | `train_class_report` | `(enrollment_id)` | 一次参训对应唯一现场报到结论 | +| 11 | `uq_student_checkin` | `train_student_checkin` | `(enrollment_id, lesson_id)` | 一个学员一堂课只有一条考勤结论 | +| 12 | `uq_student_score` | `train_student_score` | `(enrollment_id, score_item_id)` | 一个学员在某考核项只有一条最终得分 | +| 13 | `uq_student_portrait` | `train_student_portrait` | `(enrollment_id)` | 一次参训对应一份全周期评价画像 | + +--- + +## 4. 检查约束(CHECK)审查 (Round 1 扩充) + +在 Round 1 审查中,单行检查约束由原先的 4 条大幅扩充至 9 条,全面覆盖时间有效性、场地互斥、数值合法与系统字段联动: + +| 序号 | 约束名称 | 作用表 | 约束定义 | 校验逻辑目标 | +| :---: | :--- | :--- | :--- | :--- | +| 1 | `chk_lesson_time` | `train_lesson` | `start_time < end_time` | 课次开始时间必须早于结束时间 | +| 2 | `chk_lesson_venue` | `train_lesson` | `(venue_type = 1 AND class_room_id IS NOT NULL AND site_id IS NULL) OR (venue_type = 2 AND site_id IS NOT NULL AND class_room_id IS NULL)` | **场地互斥**:教室与实训场地二选一,杜绝混淆 | +| 3 | `chk_score_item_weight` | `train_score_item` | `weight >= 0.00 AND weight <= 100.00` | 单项成绩权重必须在 0~100 之间 | +| 4 | `chk_score_item_full` | `train_score_item` | `full_score > 0.00` | 满分设定必须大于 0 | +| 5 | `chk_student_score_nonneg` | `train_student_score` | `score >= 0.00` | **分值非负**:未录入不建行,录入则必须非负 | +| 6 | `chk_study_seconds` | `train_study_duration` | `study_seconds >= 0` | 累计学习秒数必须非负 | +| 7 | `chk_study_time` | `train_study_duration` | `end_time IS NULL OR start_time IS NULL OR end_time >= start_time` | **学时先后**:学习结束时间不得早于开始时间 | +| 8 | `chk_study_source_system` | `train_study_duration` | `source_event_id IS NULL OR source_system IS NOT NULL` | **单号匹配**:有外部单号时必须声明来源系统 | +| 9 | `chk_dorm_capacity` | `train_dormitory_info` | `capacity > 0` | **容量正数**:宿舍房间核定床位数必须大于 0 | + +--- + +## 5. 索引(INDEX)审查 + +[`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql) 包含外键索引、排课多维复合索引与部分唯一索引: + +1. **外键索引**:为所有 26 组外键列配置独立 B-Tree 索引,杜绝父表更新锁定子表或关联全表扫描; +2. **排课多维冲突高频复合索引**: + - 教师时间冲突检测:`idx_lesson_teacher_time (t_id, start_time, end_time)`; + - 教室时间冲突检测:`idx_lesson_classroom_time (class_room_id, start_time, end_time)`; + - 场地时间冲突检测:`idx_lesson_site_time (site_id, start_time, end_time)`; +3. **双非空过滤的部分唯一索引(Partial Unique Index)**: + - `uq_idx_study_source_event` on `train_study_duration(source_system, source_event_id) WHERE source_system IS NOT NULL AND source_event_id IS NOT NULL`; + - **审查要点**:严格杜绝 `source_system` 为空而单号有值时的幂等逃逸。 diff --git a/docs/database-v2/04_risk_review.md b/docs/database-v2/04_risk_review.md new file mode 100644 index 000000000..98482a4ed --- /dev/null +++ b/docs/database-v2/04_risk_review.md @@ -0,0 +1,101 @@ +# RailwaySystem 数据库 V2 关键设计风险深度审查报告 (R-01 ~ R-17) + +> **审查定位**:数据库底层引擎兼容性、约束边界、数据一致性与应用层 Service 协同审查 +> **技术基线**:KingbaseES V009R003C018 (`schema=public`, `database_mode=mysql`)、MyBatis 3.5.x、RuoYi-Vue 基础架构 +> **版本状态**:Round 2 审查最终静态收尾版(固化架构决策 A/B,解耦 source_type 语义,清除业务选择型默认值) + +--- + +## 风险全景概览 + +| 风险编号 | 风险主题 | 风险等级 | 影响范畴 | 本次 DDL 应对方案 | 应用层/Service 协同要求 | 决策状态 | +| :--- | :--- | :---: | :--- | :--- | :--- | :---: | +| **R-01** | 旧外键阻塞 ALTER / DROP COLUMN | **高** | 迁移执行期阻断 | 在 DDL 步骤中前置显示 `DROP CONSTRAINT IF EXISTS` | 严格按迁移编排顺序执行 | 已关闭 | +| **R-02** | identity 主键与 MyBatis 兼容性 | **中** | 写入与自增回填 | 采用标准 `GENERATED BY DEFAULT AS IDENTITY` | Mapper 继续配置 `useGeneratedKeys="true"` | 已闭环 | +| **R-03** | Kingbase 下 UNIQUE + NULL 语义 | **中** | 数据防重有效性 | 遵循 SQL 标准语义(NULL 不等),关键组合列强制非空 | 业务关键组合列强制非空或用部分索引 | 已闭环 | +| **R-04** | `id_card_hash` 唯一约束对 NULL 行为 | **低** | 学员未采身份证防重 | 建立单列 UNIQUE,允许多行 NULL 不冲突 | 应用层空串 `""` 必须显式转为 `NULL` 入库 | 已闭环 | +| **R-05** | 学习记录幂等唯一约束中 NULL 行为 | **中** | 第三方学习数据幂等 | 采用双非空条件部分唯一索引 + 联动 CHECK | 上报接口对具备单号的数据做幂等截流 | 已闭环 | +| **R-06** | 排课多维时间冲突无法靠 UNIQUE 实现 | **高** | 教学资源重叠与冲突 | 建立高频复合索引支撑区间检索,增加场地互斥 CHECK | **架构决策 A**:排课 Service 事务校验 + 行锁防重 | **已冻结决策** | +| **R-07** | 宿舍容量超额无法靠 DB 静态约束 | **高** | 宿舍超员分配超售 | 废除静态 `bed_used` 字段,加 `capacity > 0` CHECK | **架构决策 A**:住宿 Service 事务内 `SELECT FOR UPDATE` | **已冻结决策** | +| **R-08** | 成绩项目权重总和 100% 无法靠 CHECK | **中** | 成绩评估规则失衡 | 数据库仅设单行 `0~100` 标量 CHECK,不跨行聚合 | 开班与考核规则配置 Service 校验有效和等于 100% | 已闭环 | +| **R-09** | V2 表与 RuoYi Generator / BaseEntity 差异 | **中** | 代码生成与 CRUD | 数据库采用标准蛇形命名,补齐 `del_flag` 等审计列 | 后续重构 Entity 继承 BaseEntity 并重新映射 Mapper | 已闭环 | +| **R-10** | 现有 9 个 update_time 触发器安全移除 | **中** | 数据更新时间一致性 | 003 脚本显式前置 `DROP TRIGGER IF EXISTS` 卸载 | 应用层 Service 或 MyBatis 拦截器显式维护更新时间 | 已闭环 | +| **R-11** | 跨培训班核心关系一致性 (串班风险) | **高** | 业务逻辑穿透污染 | 不在子表反范式冗余 `plan_id`,保持规范化模型 | **架构决策 B**:Service 强制校验链条一致性 | **已冻结决策** | +| **R-12** | 班牌设备与课次教室空间一致性 | **中** | 考勤打卡位置穿透 | 班牌绑定教室,签到关联 lesson 与 board | 考勤 Service 校验班牌物理教室与课次教室完全匹配 | 已闭环 | +| **R-13** | 宿舍容量必须考虑培训周期时间重叠 | **高** | 跨期住宿容量误判 | 数据库不保存跨期占用,仅靠 status 会误杀 | 住宿 Service 必须按学员参训 plan 起止区间算重叠有效人数 | 已闭环 | +| **R-14** | 报到流水与参训状态变更的原子性 | **高** | 状态与报到记录脱节 | 报到记录表与参训表解耦,依赖业务事务保证 | 报到操作必须在单个本地事务内执行“插报到+改状态” | 已闭环 | +| **R-15** | 成绩记录存在性与真实 0 分语义 | **中** | 成绩录入状态失真 | 移除 DEFAULT 0,加非负 CHECK,无行代表未录成绩 | 查询综合成绩时通过左连接或外层逻辑区分未评与 0 分 | 已闭环 | +| **R-16** | 业务选择型字段违规设置数据库默认值 | **高** | 业务意图被系统静默猜测 | 彻底清除核验结果、考勤方式、场地类型等 DEFAULT | 调用方必须显式传入真实业务值,严禁自动代填 | 已闭环 | +| **R-17** | 两处 `source_type` 语义混淆与臆造枚举 | **中** | 字典定义混乱与流程固化 | 区分主档来源与参训来源,解除未定字典硬编码 | 独立字典维护,学员主档与名单来源彻底解耦 | 已闭环 | + +--- + +## 核心架构决策正式固化 (技术负责人已拍板) + +### 架构决策 A:排课与住宿并发控制方案 (冻结) +- **技术负责人拍板结论**: + 一期**坚决不引入**外部排课引擎、时间区间调度中间件或重型规则引擎。 +- **实施边界**: + 1. **数据库层**:提供针对性的多列复合索引(`idx_lesson_teacher_time`, `idx_lesson_classroom_time`, `idx_lesson_site_time`),确保范围查询毫秒级响应; + 2. **应用服务层**:排课与调课业务由 `TrainLessonService` 在 `@Transactional` 事务内执行区间交叉判断(`start_time < :newEnd AND end_time > :newStart`); + 3. **并发防护**:高并发场景下,使用 `SELECT ... FOR UPDATE` 悲观锁或分布式锁锁定教师日历/教室资源/宿舍房间,确保零时间冲突、零超售。 + +### 架构决策 B:跨培训班一致性控制规范 (防串班,冻结) +- **技术负责人拍板结论**: + **坚决不为了防止业务串班而在打卡、成绩、学习记录表中重复冗余增加 `plan_id`**,全面维持数据库 V2 的 3NF 规范化设计,防止数据更新异常与列冗余。 +- **实施边界**: + 由相关业务 Service 在执行写入前执行强一致性校验链条,任何不匹配必须当场抛出业务异常拒绝写入: + ```text + 1. 考勤打卡: + checkin.enrollment.plan_id == lesson.plan_course.plan_id + 2. 成绩录入: + student_score.enrollment.plan_id == score_item.plan_id + 3. 考核项关联课程: + IF score_item.plan_course_id IS NOT NULL: + score_item.plan_id == plan_course.plan_id + 4. 学习时长上报: + study_duration.enrollment.plan_id == plan_course.plan_id + ``` + +--- + +## 深度风险分析与防范对策 + +### R-16: 业务选择型默认值删除与责任边界 +- **问题机制**: + 若数据库为 `train_class_report.verify_result` 配置 `DEFAULT 1`(核验通过),一旦前端或接口未传该字段,数据库将自动判定该学员“核验通过”,造成假通过的安全漏洞;同样,`venue_type DEFAULT 1`、`source_type DEFAULT 1`、`checkin_way DEFAULT 1` 等均属于业务必须明确作出的选择。 +- **DDL 改造方案**: + 全面移除以下字段的默认值,强制设为 `NOT NULL`,迫使应用层显式传参: + - `train_enrollment.source_type SMALLINT NOT NULL` + - `train_enrollment.accommodation_required SMALLINT NOT NULL` + - `train_lesson.venue_type SMALLINT NOT NULL` + - `train_student_score.source_type SMALLINT NOT NULL` + - `train_student_checkin.checkin_way SMALLINT NOT NULL` + - `train_class_report.verify_result SMALLINT NOT NULL` +- **生命周期状态说明**: + 生命周期的初始状态仍保留合理默认值,例如: + - `train_class_plan.plan_status`: `DEFAULT 0` (未开始) + - `train_enrollment.enrollment_status`: `DEFAULT 0` (待报到) + - `train_accommodation.accommodation_status`: `DEFAULT 0` (预分配,由 1 修正为 0) + - `train_dorm_auth.face_sync_status`: `DEFAULT 0` (待下发) + - `train_dorm_auth.retry_count`: `DEFAULT 0` + - `train_student_checkin.manual_flag`: `DEFAULT 0` (自动) + +--- + +### R-17: 两个 source_type 语义彻底解耦 +系统在两张核心表中存在同名字段 `source_type`,必须从业务与字典层面彻底隔离,杜绝混用: + +1. **`train_student.source_type` (学员长期主档来源)**: + - **业务含义**:自然人作为学员进入铁路培训系统长期资源库的注册渠道。 + - **典型枚举**: + - `1`: 管理员手工录入 + - `2`: Excel 花名册批量导入 + - `3`: 铁路内部人力/教务业务平台同步 + - **对应字典**:`train_student_source` + +2. **`train_enrollment.source_type` (参训名单进入来源)**: + - **业务含义**:已在库学员被选拔/加入到**具体某一期培训班名单**的业务途径。 + - **设计准则**: + 清除“自主报名/单位推荐/统一调训”等未经正式拍板的臆造流程枚举,DDL 注释明确标记:“参训名单来源,具体字典值由 train_enrollment_source 定义”。 + - **对应字典**:后续由独立业务字典 `train_enrollment_source` 承载,两字段严禁共用同一字典。 diff --git a/docs/database-v2/05_execution_contract.md b/docs/database-v2/05_execution_contract.md new file mode 100644 index 000000000..b15f99593 --- /dev/null +++ b/docs/database-v2/05_execution_contract.md @@ -0,0 +1,92 @@ +# RailwaySystem 数据库 V2 迁移执行契约 (Execution Contract) + +> **适用范围**:数据库 V2 迁移在测试环境与未来生产环境中的正式执行规范 +> **技术基线**:KingbaseES V009R003C018 (`database=training`, `schema=public`, `database_mode=mysql`) +> **核心原则**:**双门禁强准入 (Gate A/B)、单文件显式事务、纯 SQL 执行器兼容、失败即刻熔断** + +--- + +## 1. 执行器与客户端兼容规范 + +为杜绝因客户端专有指令导致的语法中断或解析失败,物理迁移 SQL 已全面实施去客户端化: +1. **纯标准 SQL 语法**: + - 彻底移除 `\echo`、`\set`、`DELIMITER` 等特定客户端(如 psql/ksql/mysql)专有元命令; + - 进度提示统一采用 PL/pgSQL 标准内置机制:`RAISE NOTICE '...'`; + - 失败阻断统一采用标准异常中断:`RAISE EXCEPTION '...'`。 +2. **多客户端适配兼容性**: + - 支持通过 Kingbase 原生命令行工具 `ksql` 执行:`ksql -U kingbase -d training -f xxx.sql`; + - 支持通过通用数据库客户端(如 DBeaver、Navicat、DataGrip)以“执行脚本文件”模式执行; + - 支持通过自动化运维流水线或 JDBC Runner 顺序执行。 + +--- + +## 2. 迁移编排流水线与事务边界 + +迁移必须严格按照以下阶段顺序串行执行。**任何前置 Gate 失败或 Stage 报错,必须立即停止,绝对禁止跳步骤继续!** + +```mermaid +graph TD + GateA[Gate A: 000_tx_capability_probe.sql] -->|PASS| GateB[Gate B: 000_preflight.sql] + GateB -->|PASS| S1[Stage 1: 001_backup_current_schema.sql] + S1 --> S2[Stage 2: 002_create_new_tables.sql] + S2 --> S3[Stage 3: 003_alter_master_tables.sql] + S3 --> S4[Stage 4: 004_rebuild_business_tables.sql] + S4 --> S5[Stage 5: 005_constraints.sql] + S5 --> S6[Stage 6: 006_indexes.sql] + S6 -.->|跳过字典初始化| S7[Stage 7: 008_migrate_teacher.sql] + S7 --> S8[Stage 8: 009_drop_legacy_score.sql] + S8 --> S9[Stage 9: 010_verify.sql] + S9 --> Done[迁移完成 交付测试验收] +``` + +### 详细阶段契约表 + +| 阶段 | 脚本文件 | 事务边界 | 失败处理与影响 | +| :---: | :--- | :---: | :--- | +| **Gate A** | [`000_tx_capability_probe.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_tx_capability_probe.sql) | 探针显式事务 | **强阻断**:实证探测实例 DDL 回滚能力。若表残留立即抛出异常并中止,严禁进入后续阶段 | +| **Gate B** | [`000_preflight.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_preflight.sql) | 纯只读骨架断言 | **强阻断**:集合差集校验 14旧表PK、24旧表FK、7组UNIQUE、6个identity主键、9触发器与行数基线,任何不符立即中止 | +| **Stage 1** | [`001_backup_current_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/001_backup_current_schema.sql) | `BEGIN ... COMMIT` | **强阻断**:全量快照 14 张表至 `bak_v1_*`;若已存在备份表则阻断;失败全单回滚 | +| **Stage 2** | [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql) | `BEGIN ... COMMIT` | 创建 V2 新增 8 张表;严禁 `IF NOT EXISTS`;失败事务回滚,新表全部撤销 | +| **Stage 3** | [`003_alter_master_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql) | `BEGIN ... COMMIT` | 调整 8 张主数据表;移除旧触发器与阻断外键,添加 V2 新列;失败事务回滚 | +| **Stage 4** | [`004_rebuild_business_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql) | `BEGIN ... COMMIT` | 断言 0 行后重构 5 张业务表;失败事务回滚,原表结构保持不变 | +| **Stage 5** | [`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql) | `BEGIN ... COMMIT` | 集中注入 13 组 UNIQUE、26 组 FK (NO ACTION)、9 组 CHECK;失败事务回滚 | +| **Stage 6** | [`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql) | `BEGIN ... COMMIT` | 创建外键索引、排课多维复合索引及双非空部分唯一索引;失败事务回滚 | +| **-** | [`007_dicts.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/007_dicts.sql) | - | **【本迁移流水线跳过不执行】** 业务字典留待应用接口及权限配置阶段接入 | +| **Stage 7** | [`008_migrate_teacher.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/008_migrate_teacher.sql) | `BEGIN ... COMMIT` | 迁移并断言教师 1 行数据,固化 NOT NULL,安全清理旧列;失败事务回滚 | +| **Stage 8** | [`009_drop_legacy_score.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/009_drop_legacy_score.sql) | `BEGIN ... COMMIT` | 断言 0 行后下线旧表 `train_score_info`;失败事务回滚,旧表完好保留 | +| **Stage 9** | [`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) | 纯只读深度断言 | **强阻断**:定义级全量验收 21 表、UNIQUE 列清单、FK NO ACTION、CHECK 表达式、12 Identity、默认值及系统表零受损 | + +--- + +## 3. 已冻结的核心架构决策 (技术负责人正式拍板) + +在数据库 V2 迁移及后续业务开发中,以下两项设计原则已作为**已冻结架构决策**执行: + +### 决策 A:排课与住宿并发控制 +- **决策结论**:一期**绝不引入**外部排课引擎或复杂时间区间调度框架。 +- **技术实现**: + 1. 数据库层面配置针对性复合检索索引(`idx_lesson_teacher_time`, `idx_lesson_classroom_time`, `idx_lesson_site_time`); + 2. 业务层面由排课与住宿分配 Service 在单个本地事务内执行区间交叉检索; + 3. 必要时使用 `SELECT ... FOR UPDATE` 悲观锁锁定宿位或教师日历,保障并发一致性。 + +### 决策 B:跨培训班核心业务一致性 (防串班) +- **决策结论**:**绝不**为了防止串班而在考勤、成绩、自主学习时长等细粒度业务表冗余增加 `plan_id` 反范式列,严格保持数据库 V2 的 3NF 规范化设计。 +- **技术实现**: + 由应用层业务 Service 在写入和处理业务时强制校验一致性链条: + - `checkin.enrollment.plan_id = lesson.plan_course.plan_id` (考勤打卡班级一致) + - `student_score.enrollment.plan_id = score_item.plan_id` (成绩录入班级一致) + - 当 `score_item.plan_course_id IS NOT NULL` 时,`score_item.plan_id = plan_course.plan_id` + - `study_duration.enrollment.plan_id = plan_course.plan_id` (学习记录班级一致) + 任何不一致均由 Service 抛出业务异常阻断入库。 + +--- + +## 4. 失败处理与应急回滚规范 + +1. **单点失败即刻熔断**: + - 若流水线中任何一个脚本执行报错,**严禁跳过该步骤继续执行后续脚本**! + - 单文件具备 `BEGIN ... COMMIT`,当前失败脚本的所有物理改动会自动回滚撤销。 +2. **应急回滚调用准则**: + - 若在 Stage 2 ~ Stage 8 中途发生不可调和的异常,必须立即暂停,并由 DBA 评估调用 [`rollback/`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/) 脚本; + - 回滚流水线严格按照:`001_rollback_new_tables.sql` -> `002_rollback_modified_tables.sql` -> `003_restore_legacy_score.sql` 倒序执行; + - 回滚完毕后,必须运行 `000_preflight.sql` 校验是否完全恢复至 Phase 0.5 精确基线。 diff --git a/docs/database-v2/06_review_round1_changes.md b/docs/database-v2/06_review_round1_changes.md new file mode 100644 index 000000000..667d78bdd --- /dev/null +++ b/docs/database-v2/06_review_round1_changes.md @@ -0,0 +1,29 @@ +# RailwaySystem 数据库 V2 DDL Review 修订记录表 (Round 1) + +> **评审周期**:Round 1 +> **评审基线**:高级技术负责人第一轮人工 Review 意见 +> **状态**:**已全部闭环解决 (100% Resolved)** + +--- + +## 修改明细汇总表 + +| 问题编号 | 模块分类 | 原问题描述 | 涉及修改文件 | 具体修改内容与工程落地 | 解决状态 | +| :---: | :---: | :--- | :--- | :--- | :---: | +| **REV-01** | **P0 架构** | 破坏性迁移脚本无事务边界,遇错可能部分提交;包含客户端 `\echo` 指令 | [`001`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/001_backup_current_schema.sql)~[`009`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/009_drop_legacy_score.sql), [`05_execution_contract.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/05_execution_contract.md) | 所有物理 DDL 脚本全面使用 `BEGIN ... COMMIT` 包裹,彻底删除 `\echo` 改造为纯 SQL,建立统一执行契约文档 | **已解决** | +| **REV-02** | **P0 基线** | 缺乏迁移前置环境基线校验,可能在未知或已被破坏的环境中误执行 | [`000_preflight.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_preflight.sql) | 新增前置纯只读检查脚本,强校验当前环境必须精确符合 Phase 0.5 状态(14表、行数、触发器等),任何不符立即抛出异常中止 | **已解决** | +| **REV-03** | **P0 备份** | 001 脚本使用 `DROP TABLE IF EXISTS bak_v1_*`,误执行可能覆盖最初 V1 备份 | [`001_backup_current_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/001_backup_current_schema.sql) | 增加强防重校验,若已存在任意 `bak_v1_*` 表立即抛异常阻断,严禁自动 DROP 覆盖既有历史备份 | **已解决** | +| **REV-04** | **P0 回滚** | CTAS 无法保留约束、默认值、触发器和 Identity,不能作为回滚结构来源 | [`rollback/000_phase05_exact_schema.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/000_phase05_exact_schema.sql), [`rollback/002`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/002_rollback_modified_tables.sql), [`rollback/003`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/003_restore_legacy_score.sql) | 新增标准 Phase 0.5 完整物理 DDL;重构回滚脚本,严格基于真实 DDL 重建结构,仅用 `bak_v1_*` 还原数据,解耦结构与数据 | **已解决** | +| **REV-05** | **P0 验收** | 010_verify 很多检查仅输出文本,最终未强行阻断,存在假阳性漏报隐患 | [`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) | 重构为真正的“失败即失败”,逐表、逐列、逐外键(删除规则)、逐唯一约束、逐索引进行 PL/pgSQL 强断言,出现任何不符立即抛异常终止 | **已解决** | +| **REV-06** | **字段规范** | `train_lesson.t_id` 原定义为可空,与排课必要性要求冲突 | [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql) | 将 `train_lesson.t_id` 设为 `VARCHAR(32) NOT NULL`,并在 010 中强断言 | **已解决** | +| **REV-07** | **字段规范** | `train_teacher` 新增的 `status` 与 `del_flag` 未在迁移后固化为 NOT NULL | [`008_migrate_teacher.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/008_migrate_teacher.sql) | 迁移历史教师数据并完成数据完整性校验后,显式执行 `ALTER COLUMN SET NOT NULL` | **已解决** | +| **REV-08** | **字段规范** | `train_student_score.score` 存在未经确认的 `DEFAULT 0`,掩盖未录入状态 | [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql), [`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql) | 删除 `DEFAULT 0`,保留 `NOT NULL`,增加 `CHECK (score >= 0.00)`;未录成绩不建行,0 分代表真实得分 | **已解决** | +| **REV-09** | **字段规范** | 宿舍表 `dorm_floor` 默认值 `'1'` 和 `capacity` 默认值 `4` 属于 AI 猜测 | [`003_alter_master_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql), [`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql) | 移除所有业务默认值猜测,字段保持 NOT NULL 由管理员录入,增加 `CHECK (capacity > 0)` 约束 | **已解决** | +| **REV-10** | **字段规范** | `train_electronic_board.board_name` 包含 `DEFAULT ''` 空串伪造合法值 | [`003_alter_master_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql) | 彻底移除 `DEFAULT ''`,改为 `VARCHAR(100) NOT NULL`,由用户录入明确设备名 | **已解决** | +| **REV-11** | **业务语义** | 学员来源出现“统招学员”等高校概念,两处 `source_type` 含义含糊 | [`002`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql), [`003`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql), [`007_dicts.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/007_dicts.sql) | 彻底剥离高校术语,区分:`student.source_type` 为主档来源(管理员/Excel/平台同步);`enrollment.source_type` 为参训名单来源(指派/报名/推荐/调训) | **已解决** | +| **REV-12** | **业务语义** | 教学资源类型字典与 V2 冻结稿存在分类差异 | [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql), [`007_dicts.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/007_dicts.sql) | 严格对齐 V2 冻结稿:1文档, 2视频, 3 3D模型, 4 VR资源, 5外部教学链接, 6其他 | **已解决** | +| **REV-13** | **约束安全** | 学习记录部分唯一索引当 `source_system = NULL` 且 `source_event_id != NULL` 时发生幂等逃逸 | [`004`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql), [`005`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql), [`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql) | 索引条件修正为双非空 `WHERE source_system IS NOT NULL AND source_event_id IS NOT NULL`;增加 CHECK 约束强制单号非空时系统名必填 | **已解决** | +| **REV-14** | **依赖解耦** | 脚本滥用 `DROP TABLE ... CASCADE` 掩盖未预期依赖 | [`004`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql), [`009`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/009_drop_legacy_score.sql), [`rollback`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/rollback/) | 迁移与回滚脚本全面显式解除已知约束,改用常规 `DROP TABLE`,禁止使用 CASCADE 掩盖隐式依赖 | **已解决** | +| **REV-15** | **数据一致** | 排课场地类型与教室/实训场地未在数据库层实现单行互斥;学时时间未核验先后 | [`002`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql), [`004`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql), [`005_constraints.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/005_constraints.sql) | `train_lesson` 增加 `chk_lesson_venue` 互斥 CHECK;`train_study_duration` 增加 `end_time >= start_time` CHECK 约束 | **已解决** | +| **REV-16** | **风险扩展** | 风险报告需要补充跨班级关联、班牌一致性、宿舍重叠计算等高级业务风险 | [`04_risk_review.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/04_risk_review.md) | 在文档中新增 **R-11 至 R-15** 五项重大业务与并发一致性风险深度剖析 | **已解决** | +| **REV-17** | **执行控制** | 结构迁移阶段执行 007_dicts 会污染空库测试基线 | [`02_migration_plan.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/02_migration_plan.md), [`05_execution_contract.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/05_execution_contract.md) | 正式明确 007 在本阶段暂不执行,等后续 API/权限设计阶段统一执行 | **已解决** | diff --git a/docs/database-v2/07_review_round2_changes.md b/docs/database-v2/07_review_round2_changes.md new file mode 100644 index 000000000..888a424a3 --- /dev/null +++ b/docs/database-v2/07_review_round2_changes.md @@ -0,0 +1,143 @@ +# RailwaySystem 数据库 V2 DDL Review Round 2 变更记录 (最终静态收尾) + +> **修订阶段**:数据库 V2 DDL Review Round 2(最终静态收尾) +> **最高依据**:`RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0.md` +> **修订目标**:解决高级技术负责人 Final Static Review 提出的 7 个核心静态问题(R2-01 ~ R2-07),使数据库 V2 DDL 达到“允许进入测试迁移”状态。 + +--- + +## 变更明细清单 (R2-01 ~ R2-07) + +### R2-01: 修正住宿状态默认值 +- **问题**:`train_accommodation.accommodation_status` 原默认值为 `1`(已入住)。而正式业务流程为:名单确定 -> 报到前宿舍预分配 -> 学员报到核验 -> 展示已分配宿舍 -> 实际入住。若默认直接为已入住,与真实业务全生命周期相悖。 +- **原实现**: + ```sql + accommodation_status SMALLINT NOT NULL DEFAULT 1 + ``` +- **修改文件**: + - [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql#L142) + - [`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) +- **修改后实现**: + ```sql + accommodation_status SMALLINT NOT NULL DEFAULT 0 + ``` + 字段注释保持为:`住宿状态 (0:预分配, 1:已入住, 2:已退宿)`;`010_verify.sql` 中新增对 `accommodation_status` 默认值精确为 0 的强断言。 +- **验证方式**:静态文本检索与 `010_verify.sql` 自动化断言规则核验。 +- **是否解决**:**已彻底解决**。 + +--- + +### R2-02: 删除“业务选择型默认值” +- **问题**:原 SQL 中对部分必须由调用方主动选择的业务输入字段设置了 `DEFAULT 0` 或 `DEFAULT 1`,特别是 `verify_result DEFAULT 1`(核验通过),会导致应用层若漏传该参数,数据库自动判定通过,掩盖业务逻辑缺陷;同样,考勤方式、场地类型、住宿需求、来源类型等必须由业务明确提供,不允许数据库代替调用方猜测。 +- **原实现**: + - `train_enrollment.source_type`: `DEFAULT 1` + - `train_enrollment.accommodation_required`: `DEFAULT 0` + - `train_lesson.venue_type`: `DEFAULT 1` + - `train_student_score.source_type`: `DEFAULT 1` + - `train_student_checkin.checkin_way`: `DEFAULT 1` + - `train_class_report.verify_result`: `DEFAULT 1` +- **修改文件**: + - [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql) + - [`004_rebuild_business_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/004_rebuild_business_tables.sql) + - [`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) +- **修改后实现**: + 上述 6 个字段全部移除 `DEFAULT` 声明,仅保留 `SMALLINT NOT NULL`。`010_verify.sql` 中增加只读断言:上述字段的 `column_default` 必须全部为 `NULL`,若存在默认值当场抛异常。 +- **验证方式**:静态 grep 全局检索与 `010_verify.sql` 断言校验。 +- **是否解决**:**已彻底解决**。 + +--- + +### R2-03: 清除 Enrollment 来源的臆造枚举并解耦双 source_type +- **问题**:`train_enrollment.source_type` 注释中原先硬编码了“1:组织指派, 2:自主报名, 3:单位推荐, 4:统一调训”,这属于未经正式会议冻结的臆造业务流程枚举;且容易与 `train_student.source_type`(学员长期主档来源)发生混淆。 +- **原实现**: + ```sql + COMMENT ON COLUMN train_enrollment.source_type IS '进入本期培训名单的来源 (1:组织指派, 2:自主报名, 3:单位推荐, 4:统一调训)'; + ``` +- **修改文件**: + - [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql#L94) + - [`04_risk_review.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/04_risk_review.md) + - [`01_table_inventory.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/01_table_inventory.md) +- **修改后实现**: + - 注释修改为: + ```sql + COMMENT ON COLUMN train_enrollment.source_type IS '参训名单来源,具体字典值由 train_enrollment_source 定义'; + ``` + - 文档中明确解耦定义: + 1. `train_student.source_type`:学员长期主档进入系统的来源(管理员录入/Excel导入/平台同步); + 2. `train_enrollment.source_type`:人员进入“本期培训名单”的来源,由独立字典承载,严禁共用同一模糊枚举。 +- **验证方式**:审查注释与文档的一致性。 +- **是否解决**:**已彻底解决**。 + +--- + +### R2-04: Preflight 升级为真正 Phase 0.5 精确骨架校验 +- **问题**:原 `000_preflight.sql` 仅做了表存在性、行数和少量代表性约束判断,未能覆盖完整的 Phase 0.5 物理结构骨架,若存在“少一个正确FK + 多一个错误FK”的对冲情况可能假阳性通过。 +- **原实现**:部分抽检 + 触发器循环判断。 +- **修改文件**: + - [`000_preflight.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_preflight.sql) +- **修改后实现**: + 采用 CTE `VALUES(...)` 构造预期基线集合,与系统目录做全集合差集比对: + - 14 个旧表 PK (table_name + constraint_name + column_name) + - 6 个 Phase 0.5 identity 自增主键 (table_name + column_name + is_identity = 'YES') + - 7 组业务 UNIQUE 约束 (table_name + constraint_name + column_list) + - 24 组业务 FK 约束 (table_name + constraint_name + column_name + foreign_table_name + foreign_column_name) + - 9 个 update_time 触发器 (table_name + trigger_name) + - 行数基线:`train_teacher` 精确 1 行,其余 13 张旧表精确 0 行 + - 8 张 V2 表均不得存在,public.sys_* 表零受损 + - 任何 missing / unexpected / definition mismatch 立即抛出 `RAISE EXCEPTION`。 +- **验证方式**:对比 `rollback/000_phase05_exact_schema.sql` 校验集合完备性。 +- **是否解决**:**已彻底解决**。 + +--- + +### R2-05: 010_verify 升级为“定义级验收” +- **问题**:原 010 验收脚本主要核验对象名称存在性,对约束内部的具体列排序、外键删除规则、CHECK 表达式逻辑等未做定义级校验。 +- **原实现**:检查约束名称与 `count(*) = 26` 等数量判断。 +- **修改文件**: + - [`010_verify.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/010_verify.sql) +- **修改后实现**: + - **UNIQUE 验收**:必须断言 `table_name` + `constraint_name` + `column_list`(如 `train_enrollment(plan_id, student_id)`); + - **FK 验收**:必须断言源表、源列、目标表、目标列及 `delete_rule = 'NO ACTION'`,严禁 CASCADE; + - **CHECK 验收**:按约束名逐项检索表达式,校验包含预期表达式(时间先后、场地二选一、权重0~100、满分>0、得分非负、时长非负、单号系统联动、容量>0); + - **Identity 验收**:逐项确认 12 张业务表的主键列名且 `is_identity = 'YES'`; + - **Index 验收**:确认 3 组排课复合时间列排序;确认 `uq_idx_study_source_event` 的 `(source_system, source_event_id)` 及双非空 WHERE 条件; + - **默认值验收**:强断言 `accommodation_status` 默认为 0,强断言 6 个业务选择型字段无默认值。 +- **验证方式**:静态代码比对与逻辑推演。 +- **是否解决**:**已彻底解决**。 + +--- + +### R2-06: 正式迁移对象删除 IF NOT EXISTS +- **问题**:V2 是一次从已知 Phase 0.5 基线出发的严格版本迁移,已有 Preflight 保证环境纯净。若正式建表或建索引保留 `IF NOT EXISTS`,一旦因历史残留存在同名但结构错误的脏对象,DDL 将静默跳过真正正确的对象创建,掩盖严重隐患。 +- **原实现**: + - `CREATE TABLE IF NOT EXISTS train_...` (002) + - `ADD COLUMN IF NOT EXISTS ...` (003) + - `CREATE INDEX IF NOT EXISTS ...` (006) +- **修改文件**: + - [`002_create_new_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/002_create_new_tables.sql) + - [`003_alter_master_tables.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/003_alter_master_tables.sql) + - [`006_indexes.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/006_indexes.sql) +- **修改后实现**: + - 002 中 8 张新增表全部改为严格 `CREATE TABLE`; + - 003 中新增字段全部改为严格 `ADD COLUMN`; + - 006 中所有索引全部改为严格 `CREATE INDEX` 与 `CREATE UNIQUE INDEX`; + - 仅保留防御性卸载旧对象的 `DROP CONSTRAINT IF EXISTS` 和 `DROP TRIGGER IF EXISTS`。 +- **验证方式**:全局 grep 检查 `IF NOT EXISTS` 出现位置并逐一确认合理性。 +- **是否解决**:**已彻底解决**。 + +--- + +### R2-07: 增加 DDL Transaction Probe Gate +- **问题**:迁移执行契约依赖事务的原子性(`BEGIN ... COMMIT / ROLLBACK`)。当前环境为 KingbaseES V009R003C018 (`database_mode=mysql`),需在正式执行 V2 前实证验证当前数据库实例对 DDL 的事务回滚支持能力。 +- **原实现**:未设立 DDL 事务能力前置探针。 +- **修改文件**: + - 新增:[`RuoYi-Vue/sql/train/v2/000_tx_capability_probe.sql`](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/sql/train/v2/000_tx_capability_probe.sql) + - [`05_execution_contract.md`](file:///Users/wzb/Desktop/项目/RailwaySystem/docs/database-v2/05_execution_contract.md) +- **修改后实现**: + - 创建独立的安全探针脚本(作为 Gate A): + - **Phase A**:`BEGIN -> CREATE TABLE public.__railway_v2_tx_probe -> ALTER TABLE -> ROLLBACK`; + - **Phase B**:只读断言 `__railway_v2_tx_probe` 是否存在。若残留则强行抛出 `RAISE EXCEPTION` 阻断后续所有迁移; + - 绝不读取或修改任何业务表或系统表,仅使用专用临时对象。 + - 在执行契约中将 Gate A 设定为执行流水线第一道强制关卡。 +- **验证方式**:审查探针脚本的幂等性与零副作用安全性。 +- **是否解决**:**已彻底解决**。