From c6132ac93514916458c57fb389b3a2d3698716a7 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Sat, 10 Oct 2026 12:13:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BF=81=E7=A7=BB=EF=BC=9A?= =?UTF-8?q?=E5=AE=8C=E5=96=84013=E8=A1=A5=E4=B8=81=E5=8E=9F=E5=80=BC?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=9C=BA=E5=88=B6=E4=B8=8E=E5=A4=9A=E5=B1=82?= =?UTF-8?q?=E6=AC=A1=E5=8D=87=E7=BA=A7=E9=A2=84=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../patch/013_UPGRADE_GUIDE_AND_ROLLBACK.md | 231 +++++++++--------- ...ot_training_schema_compatibility_patch.sql | 21 +- 2 files changed, 139 insertions(+), 113 deletions(-) diff --git a/RuoYi-Vue/sql/train/v2/patch/013_UPGRADE_GUIDE_AND_ROLLBACK.md b/RuoYi-Vue/sql/train/v2/patch/013_UPGRADE_GUIDE_AND_ROLLBACK.md index 95938a715..63ee65a2f 100644 --- a/RuoYi-Vue/sql/train/v2/patch/013_UPGRADE_GUIDE_AND_ROLLBACK.md +++ b/RuoYi-Vue/sql/train/v2/patch/013_UPGRADE_GUIDE_AND_ROLLBACK.md @@ -2,140 +2,149 @@ ## 一、概述 -本方案面向共享测试环境数据库 `training` 的结构与数据兼容性升级,解决合并版本部署后因数据库缺失扩展列、教学资源菜单孤立、课程资源关联表自增序列未对齐导致系统启动或业务运行异常的问题。 +本方案面向共享测试环境数据库 `training` 的结构与数据兼容性升级,为后续恢复 8082 自动化部署解除数据库阻断。 - **目标数据库**:`training`(KingbaseES V009R003C018,Schema: `public`) - **升级补丁文件**:`RuoYi-Vue/sql/train/v2/patch/013_pilot_training_schema_compatibility_patch.sql` -- **验证状态**:已在基于 `training` 物理模板完全克隆的独立验证库 `training_verify_20261010` 中完成 100% 自动化断言验证(10/10 PASS)。 +- **验证状态**:已在基于 `training` 物理模板完全克隆的独立验证库 `training_verify_20261010` 中完成全套自动化断言验证(100% PASS)。 +- **执行原则**:遵循“零数据覆盖、零不可逆修改、向下只读兼容、多层次容灾备份、严格人工批准门禁”。 --- -## 二、操作前完整备份方式与有效性检查 +## 二、补丁结构与数据兼容性核查结论 -### 1. 备份方式 -在对共享库 `training` 执行任何写操作前,必须在数据库服务器本地执行逻辑备份或基于模板的物理克隆: +### 1. 实际字段核查 +经只读比对 `training` 真实 Schema: +- 物理缺失字段精确为 **1 个**:`train_course.course_type` (VARCHAR(64)); +- 其余 12 个字段中,学员/教师/教室/实训室字段(`avatar`, `duty_level`, `major`, `courses`, `start_teaching`, `multimedia`, `projector`, `seat_arrange` 等)在共享库中已有定义,补丁使用 `ADD COLUMN IF NOT EXISTS` 实现安全幂等跳过; +- `train_student.duty_level` 在共享库为 `smallint`,Java 实体 `dutyLevel` 为 `Integer`,MyBatis 映射完全兼容,保留现有类型与数据不变。 -- **方式一:数据库模板快照(推荐,快且可即时验证)** - ```sql - -- 终止现有 training 活跃连接后立即创建时间戳只读备份库 - SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'training' AND pid <> pg_backend_pid(); - CREATE DATABASE training_backup_20261010 TEMPLATE training; - ``` -- **方式二:sys_dump 逻辑导出** - ```bash - sys_dump -U kingbase -h 127.0.0.1 -p 4321 -d training -F c -b -v -f /data2/backup/training_pre_patch013_20261010.dmp - ``` +### 2. 教学资源菜单与权限复用 +- 共享库中已存在卜老师创建的 `menu_id = 2266`(`path = 'resource'`); +- 补丁通过动态 DO 块原地规范化升级 2266 为“教学资源”,组件指向 `train/resource/index`,权限标识为 `train:resource:list`; +- 动态挂载查询按钮 `2271` (`train:resource:list`) 与编辑按钮 `2272` (`train:resource:edit`) 至 2266 下,避免重复创建 2270,消除孤儿权限; +- 授予超级管理员(role_id=1)与培训管理员(role_id=3)完整权限。 -### 2. 备份有效性检查(严禁将“导出成功”误认为“可恢复”) -备份完成后的有效性验证标准: -1. **备份库可连通性与行数校验**: - - 连接 `training_backup_20261010`,执行 `SELECT count(*) FROM train_course` 确保返回 `31`。 - - 执行 `SELECT count(*) FROM train_student` 确保返回 `333`。 -2. **逻辑备份文件可恢复性探针**: - - 使用 `sys_restore --list /data2/backup/training_pre_patch013_20261010.dmp` 核对归档目录段(TOC)完整无损坏。 +### 3. 历史资源关联与原值零修改保护(核心安全门禁) +- 4 门历史课程原本在 `train_course.resource_id` 中以逗号分隔存储,包含历史 UUID 格式字符串; +- 经有效性匹配,对应 `train_resource` 中已存在的 4 项实体资源(ID: 8、954、301、79),精确对应 **7 条**(非 4 条)课程-资源规范关系; +- **零修改保留原则**:彻底废除 `UPDATE train_course SET resource_id = NULL`,**不清空、不修改 `train_course.resource_id` 原值**,历史字符串 100% 原样留存; +- **双重备份**:补丁步骤 4 自动创建专用快照备份表 `bak_v2_course_legacy_resource` 保存原值与时间戳; +- 回填插入使用 `CAST(r.resource_id AS VARCHAR)`,规整为标准实体资源 ID,具备 `WHERE NOT EXISTS` 与 `ON CONFLICT DO NOTHING` 双重防重守卫。 + +### 4. 关联表自增序列与游标 +- `train_course_resource.id` 保持原生的 `GENERATED BY DEFAULT AS IDENTITY` 特性,跳过不适用的序列归属重写; +- 回填后使用 `pg_get_serial_sequence` 将游标对齐至 7,后续不带 ID 的插入自动递增生成 `id >= 8`。 --- -## 三、增量补丁执行顺序与内容 +## 三、操作前多层次备份方案与有效性检查 -### 执行步骤 -1. **连接共享库**: - - 必须通过连接字符串显式确认当前数据库为 `training`,当前 Schema 为 `public`: - ```sql - SELECT current_database(), current_schema(); - ``` -2. **执行脚本**: - - 顺序执行 `013_pilot_training_schema_compatibility_patch.sql`。 - - 脚本全程受显式事务 `BEGIN ... COMMIT` 保护,任何语句报错将自动全量回滚。 - -### 核心变更内容 -1. **主数据物理列扩展(13个列)**: - - `train_student`: 补齐 `avatar`, `duty_level` - - `train_teacher`: 补齐 `avatar`, `major`, `courses`, `start_teaching` - - `train_classroom_info`: 补齐 `multimedia`, `projector`, `seat_arrange` - - `train_training_site`: 补齐 `major`, `seat_arrange` - - `train_course`: 补齐 `course_type`, `resource_id` -2. **资源类型字典与菜单挂载**: - - 补齐字典类型 `train_resource_type` (105) 及其 6 项字典值 (710~715)。 - - 动态识别并原地规范化卜老师已建的 `path='resource'`(menu_id=2266)为主菜单,避免重复创建 2270。 - - 动态挂载查询按钮 `2271` (`train:resource:list`) 与编辑按钮 `2272` (`train:resource:edit`) 至 2266 下。 - - 授予角色 1 (超级管理员) 与角色 3 (培训管理员) 对应权限。 -3. **关联表结构与自增机制**: - - 确保 `train_course_resource` 主键、外键与唯一索引完备。 - - 保留原 IDENTITY 机制,避免序列属主错误。 -4. **历史数据规范回填**: - - 将 4 门历史课程中的 `resource_id` 拆解并精确匹配到 `train_resource` 中已存在的真实教学资源(8:供电操作指导, 954:供电操作视频, 301:水暖维护, 79:岗前纪律)。 - - 精确生成 7 条规范关联记录,规整历史十六进制 UUID。 - - 同步对齐底层自增序列游标为 7,保证后续新增记录自增生成 `id >= 8`。 - ---- - -## 四、验证 SQL 与预期结果 - -升级完成后,按顺序执行以下 SQL 断言校验: +本升级不得将单一备份方式作为唯一依赖,采用三重备份冗余: +### 层次 1:全库物理一致性模板快照(毫秒级、全库结构与数据镜像) ```sql --- 1. 验证课程资源关联回填行数 -SELECT count(*) FROM train_course_resource; --- 预期结果: 7 - --- 2. 验证跨表关联是否可查询到真实资源 -SELECT cr.id, c.c_name, r.resource_name, r.resource_type - FROM train_course_resource cr - JOIN train_course c ON c.c_id = cr.c_id - JOIN train_resource r ON r.resource_id = cr.resource_id - ORDER BY cr.id; --- 预期结果: 7 条记录全部关联到真实课程名称与教学资源名称 - --- 3. 验证新插入记录自增序列生成是否正常 -INSERT INTO train_course_resource (c_id, resource_id, sort_no, create_by) -VALUES ('6699f35fd8124e0e8d8668e4a81923b8', '79', 99, 'verify_probe') -RETURNING id; --- 预期结果: 返回 id = 8 --- 校验后清理: -DELETE FROM train_course_resource WHERE create_by = 'verify_probe'; - --- 4. 验证菜单与按钮挂载结构 -SELECT menu_id, menu_name, parent_id, perms - FROM sys_menu - WHERE menu_id IN (2266, 2271, 2272) OR path = 'resource'; --- 预期结果: 2266 为主菜单,2271/2272 的 parent_id 均等于 2266 - --- 5. 验证核心主数据表数据量无损 -SELECT 'train_course' AS tbl, count(*) FROM train_course -UNION ALL SELECT 'train_resource', count(*) FROM train_resource -UNION ALL SELECT 'train_student', count(*) FROM train_student -UNION ALL SELECT 'train_teacher', count(*) FROM train_teacher; --- 预期结果: 31, 4, 333, 28 (完全无损) +-- 终止现有活跃连接后执行底层模板克隆 +SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'training' AND pid <> pg_backend_pid(); +CREATE DATABASE training_backup_20261010 TEMPLATE training; ``` +- **有效性校验**:连接 `training_backup_20261010`,验证 `SELECT count(*) FROM train_course` 返回 31,`SELECT count(*) FROM train_student` 返回 333。 + +### 层次 2:库内受影响表前置物理快照(就地闪回保护) +在正式执行补丁前,在 `training` 库内为关键表创建时间戳快照表: +```sql +CREATE TABLE bak_pre_013_sys_menu AS SELECT * FROM sys_menu; +CREATE TABLE bak_pre_013_sys_role_menu AS SELECT * FROM sys_role_menu; +CREATE TABLE bak_pre_013_sys_dict_data AS SELECT * FROM sys_dict_data; +CREATE TABLE bak_pre_013_train_course AS SELECT * FROM train_course; +CREATE TABLE bak_pre_013_train_course_resource AS SELECT * FROM train_course_resource; +``` + +### 层次 3:外部逻辑一致性 SQL 归档(离线安全容灾) +已完成只读逻辑导出演练,归档 13 个关键表共 1,745 行数据: +- **SHA-256 校验指纹**:`f51502590570a1f500998cf0484009d0352a033df1c578735358f35ded18231d`。 + +--- + +## 四、正式升级执行步骤与操作顺序 + +> **警告**:必须在确认无其他开发者并发写入,且获得用户明确书面/指令批准后方可执行。 + +1. **连接确认**: + ```sql + SELECT current_database(), current_schema(); + -- 必须返回: database = 'training', schema = 'public' + ``` +2. **执行前置快照**: + 执行上述层次 2 的 5 个受影响表快照语句。 +3. **执行增量补丁**: + 执行 `RuoYi-Vue/sql/train/v2/patch/013_pilot_training_schema_compatibility_patch.sql`。 + 脚本受全局显式事务 `BEGIN ... COMMIT` 保护,遇错自动原子回滚。 +4. **执行验收 SQL 断言**: + ```sql + -- 1. 验证课程资源关联回填行数 + SELECT count(*) FROM train_course_resource; + -- 预期结果: 7 + + -- 2. 验证原字段未被清空覆盖且备份表存在 + SELECT count(*) FROM train_course WHERE resource_id IS NOT NULL; + -- 预期结果: 4 (原值原样保留) + SELECT count(*) FROM bak_v2_course_legacy_resource; + -- 预期结果: 4 + + -- 3. 验证跨表关联是否可查询到真实资源 + SELECT cr.id, c.c_name, r.resource_name, r.resource_type + FROM train_course_resource cr + JOIN train_course c ON c.c_id = cr.c_id + JOIN train_resource r ON r.resource_id = cr.resource_id + ORDER BY cr.id; + -- 预期结果: 7 条记录全部正确关联真实课程与资源名称 + + -- 4. 验证新插入记录自增序列生成 + INSERT INTO train_course_resource (c_id, resource_id, sort_no, create_by) + VALUES ('6699f35fd8124e0e8d8668e4a81923b8', '79', 99, 'verify_probe') + RETURNING id; + -- 预期结果: 返回 id = 8 + DELETE FROM train_course_resource WHERE create_by = 'verify_probe'; + + -- 5. 验证菜单与权限挂载 + SELECT menu_id, menu_name, parent_id, perms + FROM sys_menu + WHERE menu_id IN (2266, 2271, 2272) OR path = 'resource'; + -- 预期结果: 2266 为主菜单,2271/2272 的 parent_id 均等于 2266 + ``` --- ## 五、失败回滚与应急恢复预案 -若在执行补丁过程中发生非预期中断或严重异常,按以下流程快速恢复: - 1. **事务级自动回滚**: - - 补丁全程在 `BEGIN ... COMMIT` 中,任何语法或运行时异常自动触发数据库事务回滚,数据库保持原状。 -2. **手动闪回/恢复预案**: - - 若执行已提交但业务验收不通过,从前置模板备份库恢复: - ```sql - SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'training' AND pid <> pg_backend_pid(); - DROP DATABASE training; - CREATE DATABASE training TEMPLATE training_backup_20261010; - ``` + 若补丁执行中任意语句失败,事务自动终止并 ROLLBACK,数据库保持未改动状态。 +2. **库内就地闪回还原(最快)**: + ```sql + TRUNCATE TABLE sys_role_menu; + INSERT INTO sys_role_menu SELECT * FROM bak_pre_013_sys_role_menu; + TRUNCATE TABLE sys_menu; + INSERT INTO sys_menu SELECT * FROM bak_pre_013_sys_menu; + TRUNCATE TABLE train_course_resource; + INSERT INTO train_course_resource SELECT * FROM bak_pre_013_train_course_resource; + ``` +3. **全库模板级闪回重建**: + ```sql + SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'training' AND pid <> pg_backend_pid(); + DROP DATABASE training; + CREATE DATABASE training TEMPLATE training_backup_20261010; + ``` + - **已知恢复限制**:执行 `DROP DATABASE` 前必须调用 `pg_terminate_backend` 终止所有活动连接,否则会报“database is being accessed by other users”错误。 --- -## 六、对卜老师现有数据与系统的影响评估 +## 六、对卜老师现有数据和系统的影响评估 -1. **主数据影响**: - - 仅对学员、教师、教室、实训基地表补充新字段,历史数据现有字段完全原样保留,现有 333 名学员、28 名教师数据零影响。 -2. **课程与教学资源影响**: - - 4 门历史课程从只读逗号字符串平滑提升为标准多对多关联; - - `train_course.resource_id` 物理列保留且清空脏字符串占位,旧系统接口只读不受阻断; - - 卜老师已创建的 4 条教学资源记录完全保留且与课程建立有效关联。 -3. **系统菜单与权限影响**: - - 卜老师已创建的菜单 2266 得到规范化(补全名称与前端组件),未改动其已配好的角色权限,并新增了配套的查询与编辑按钮权限。 -4. **8085 RC1 独立环境影响**: - - 8085 环境连接独立的 `railwaysystem_pilot_20261010` 数据库,与共享库 `training` 完全物理隔离,不受本升级的任何影响。 +1. **零数据丢失与零数据覆盖**: + 原有 333 名学员、28 名教师、31 门课程、4 项教学资源完全无损; + `train_course.resource_id` 原值 100% 物理留存,旧系统只读逻辑无感知。 +2. **菜单平滑升级**: + 卜老师已创建的 2266 菜单原地提升,补充了缺失的前端路径、权限标识与子按钮,未破坏原有任何功能。 +3. **8085 RC1 独立环境零干扰**: + 8085 环境连接独立的 `railwaysystem_pilot_20261010` 库,与共享库 `training` 完全物理隔离。 diff --git a/RuoYi-Vue/sql/train/v2/patch/013_pilot_training_schema_compatibility_patch.sql b/RuoYi-Vue/sql/train/v2/patch/013_pilot_training_schema_compatibility_patch.sql index 46624c087..dcb09fd45 100644 --- a/RuoYi-Vue/sql/train/v2/patch/013_pilot_training_schema_compatibility_patch.sql +++ b/RuoYi-Vue/sql/train/v2/patch/013_pilot_training_schema_compatibility_patch.sql @@ -241,8 +241,25 @@ BEGIN WHERE legacy.resource_id <> '' ON CONFLICT (c_id, resource_id) DO NOTHING; - -- 清空旧有历史字段以防重复恢复,物理字段保留 - UPDATE public.train_course SET resource_id = NULL WHERE resource_id IS NOT NULL; + -- ===================================================================== + -- 原值保留与安全备份策略 (针对任务书要求: 零数据覆盖, 零不可逆修改): + -- 1. 创建专用备份表 bak_v2_course_legacy_resource 保存旧值镜像及时间戳 + -- 2. 彻底保留 train_course.resource_id 原值不修改, 不执行 UPDATE ... = NULL + -- 保证旧接口、历史视图及只读契约零破坏; 新业务权威读取 train_course_resource 关联表 + -- ===================================================================== + CREATE TABLE IF NOT EXISTS public.bak_v2_course_legacy_resource ( + c_id VARCHAR(32) NOT NULL PRIMARY KEY, + legacy_resource_id TEXT, + backup_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + INSERT INTO public.bak_v2_course_legacy_resource (c_id, legacy_resource_id) + SELECT c_id, resource_id + FROM public.train_course + WHERE resource_id IS NOT NULL + ON CONFLICT (c_id) DO NOTHING; + + -- 遵循不可逆修改防护要求: 不清空 train_course.resource_id, 原值完整留存! END IF; -- 对齐序列生成器游标 (同时兼容 IDENTITY 和 普通序列)