Files
railwaysystem/docs/codebase-audit/18_phase05_runtime_fix.md
wangzhibiao 4b450f46af docs(审计): 输出Phase 0.5基础运行修复与端到端验证审计报告
- 发布 docs/codebase-audit/18_phase05_runtime_fix.md,完整记录 search_path 实验结果、数据源双保险配置方案、登录链验证数据及代码生成器元数据验证
- 在 docs/README.md 核心综述清单中收录 18_phase05_runtime_fix.md 并更新审计报告索引至 19 篇
2026-09-14 00:56:18 +08:00

253 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 0.5 基础运行修复审计报告
> **基线状态说明**
> - **开发基线**:`main@e12eda05`
> - **业务依据**:`doc/RailwaySystem 智慧培训系统一期 PRD V1.0.md`
> - **数据库实例**:KingbaseES V009R003C018 (`database=training`, `schema=public`, `database_mode=mysql`)
> - **本阶段定位**:基础设施层兼容性修复(解决 RuoYi + Kingbase 基础运行冲突),不涉及任何业务模型修改与数据库结构破坏。
---
## 1. 修改前问题
在 Phase 0 审计中发现,RuoYi 与 KingbaseES 集成存在两个阻塞基础运行的核心冲突:
### 1.1 `sys_*` 系统表与 Kingbase 系统目录视图冲突
1. **隐式模式搜索优先级**:KingbaseES 当前连接默认的有效模式搜索顺序(`current_schemas(true)`)为 `{sys, pg_catalog, sys_catalog, public}`。
2. **系统同名视图拦截**:Kingbase 内置系统目录模式 `sys_catalog` 中预置了 `sys_user`、`sys_role`、`sys_config` 等系统级视图(如 `sys_catalog.sys_user` 包含 `usesysid`, `userepl` 等列,无 `user_id` 列)。
3. **若依未限定 SQL 命中异常**:RuoYi 核心系统 Mapper(如 [SysUserMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml)、[SysRoleMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml)、[SysConfigMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml))普遍使用未限定表名查询(`from sys_user`、`from sys_role`、`from sys_config`)。
4. **运行后果**:执行未限定查询时直接命中 `sys_catalog` 视图,抛出 `ERROR: column "user_id" does not exist`、`column "role_id" does not exist`,导致登录认证与系统初始化彻底瘫痪。
### 1.2 代码生成器混淆 database 与 schema
1. **函数语义错位**:代码生成器 [GenTableMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml) 与 [GenTableColumnMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml) 中使用 `where table_schema = (select current_database())`。
2. **检索为空**:在 Kingbase 体系下,`current_database()` 返回的是数据库名(`training`),而实际表与字段所属的元数据模式为 `public`(`information_schema.tables.table_schema = 'public'`)。
3. **运行后果**:代码生成器在“导入表”与“列信息加载”时无法匹配到任何物理表(返回 0 张表、0 个字段),无法支持后续的模型逆向工程与代码生成。
---
## 2. search_path 实验结果
通过原生 Kingbase JDBC 驱动(`com.kingbase8.Driver` 8.6.0)在当前开发环境建立隔离测试 Session,实测数据如下:
### 2.1 默认连接探测
```sql
show search_path;
-- 输出: public
select current_schemas(true);
-- 输出: {sys, pg_catalog, sys_catalog, public}
select user_id from sys_user;
-- 输出异常: ERROR: column "user_id" does not exist
-- 提示: Hint: Perhaps you meant to reference the column "sys_user.usesysid" or the column "sys_user.userepl".
```
**结论**:默认虽然 `search_path=public`,但 Kingbase 将系统目录 `sys_catalog` 隐式置于 `public` 之前,未限定表名必定命中系统视图。
### 2.2 设置 search_path 探测
在同一 Session 中显式执行:
```sql
SET search_path TO public, sys_catalog;
```
再次查询状态与数据:
```sql
show search_path;
-- 输出: public, sys_catalog
select current_schemas(true);
-- 输出: {sys, pg_catalog, public, sys_catalog}
```
验证各系统表命中情况:
| SQL 探测语句 | 执行结果 | 判定 |
| --- | --- | --- |
| `select user_id from sys_user;` | 成功返回 1 行 (`user_id=1`) | 稳定命中 `public.sys_user` |
| `select role_id from sys_role;` | 成功返回 0 行 (表结构识别正确) | 稳定命中 `public.sys_role` |
| `select config_id from sys_config;` | 成功返回 0 行 (表结构识别正确) | 稳定命中 `public.sys_config` |
| `select count(*) from sys_menu;` | 成功返回 106 | 稳定命中 `public.sys_menu` |
| `select count(*) from sys_dept;` | 成功返回 15 | 稳定命中 `public.sys_dept` |
| `select count(*) from sys_dict_type;` | 成功返回 44 | 稳定命中 `public.sys_dict_type` |
| `select count(*) from sys_dict_data;` | 成功返回 500 | 稳定命中 `public.sys_dict_data` |
**实验结论**:只要显式将 `sys_catalog` 置于 `public` 之后,`current_schemas(true)` 中 `public` 就会优先于 `sys_catalog`,所有未限定表名的查询均能稳定、准确地命中 `public` 业务表。
---
## 3. 最终 schema 解决方式
### 3.1 架构设计选型
为满足“只影响 RailwaySystem 连接、不修改 Kingbase 全局配置、不批量修改所有 RuoYi Mapper、修改高度集中”的要求,采用**应用连接池层双保险适配方案**:
1. **第一层:JDBC URL `currentSchema` 参数**
- 将 JDBC URL 中的 `currentSchema=public` 升级为 `currentSchema=public,sys_catalog`。
- Kingbase JDBC 驱动在建立物理 Socket 握手初始化时,直接依据该参数初始化会话 `search_path`。
2. **第二层:Druid 连接池 `connectionInitSqls` 机制**
- 在 Druid 数据源配置中声明:`connectionInitSqls: ["SET search_path TO public, sys_catalog"]`。
- DruidDataSource 保证在每次物理连接创建(`createPhysicalConnection`)完成后,都会执行一次该初始化 SQL,杜绝连接归还与重用过程中可能出现的模式搜索漂移。
### 3.2 方案优势
- **无侵入性**:无需修改任何 `Sys*Mapper.xml` 业务 SQL;
- **环境安全**:完全在应用连接会话级(Session Scope)生效,零触碰 Kingbase 数据库实例级全局参数;
- **幂等稳定**:连接池无论是初始建立还是故障重连,均自动具备正确的优先级。
---
## 4. 修改文件列表
本轮 Phase 0.5 基础运行修复严格限制在基础设施与元数据映射层,共修改 3 个文件:
| 序号 | 文件路径 | 修改性质 | 核心修改说明 |
| --- | --- | --- | --- |
| 1 | [application-druid.yml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml) | 运行配置 | master 数据源 URL 增加 `currentSchema=public,sys_catalog`,并注入 `connectionInitSqls` |
| 2 | [GenTableMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml) | 框架映射 | 将表检索中 3 处 `(select current_database())` 改为 `(select current_schema())` |
| 3 | [GenTableColumnMapper.xml](file:///Users/wzb/Desktop/项目/RailwaySystem/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml) | 框架映射 | 将列检索中 1 处 `(select current_database())` 改为 `(select current_schema())` |
---
## 5. 登录链验证结果
在后端与前端完整启动后,对 RuoYi 核心认证链执行端到端自动化探测,验证数据汇总如下:
| 步骤 | 请求方式与端点 | HTTP 状态 | 业务响应码 | 核心响应要素与凭据 | 状态判定 |
| --- | --- | --- | --- | --- | --- |
| 1 | `GET /captchaImage` | 200 OK | `code: 200` | 返回图片 Base64,签发 `uuid: c91ad82eec14407ea33a3207de3bec05` | 成功 |
| 2 | `POST /login` | 200 OK | `code: 200` | 账号 `admin`,获得 JWT 令牌(长度 203,前缀 `eyJhbGciOiJIUzUxMiJ9...`) | 成功 |
| 3 | `GET /getInfo` | 200 OK | `code: 200` | `userId=1`, `userName=admin`, `roles=['admin']`, `permissions=['*:*:*']` | 成功 |
| 4 | `GET /getRouters` | 200 OK | `code: 200` | 成功拉取 10 个顶层动态路由(Person, Equip, Coursemgr, Monitor, Result, Tool, Self, Analysis, System 等) | 成功 |
| 5 | 前端入口 `GET http://localhost:8080` | 200 OK | - | 前端工程完整返回 index.html,资源正常加载 | 成功 |
> **说明**:当前 `public.sys_role` 中为 0 条记录,本次验证充分利用了 RuoYi 原生超级管理员(`admin` / `userId=1`)的豁免与全权限逻辑,证实认证主干通路在 Kingbase 环境下已彻底畅通。
---
## 6. Generator 修改方式
### 6.1 语义区分与原则
- **明确概念边界**:`database`(在 Kingbase 为 `training`)与 `schema`(在 Kingbase 为 `public`)在非 MySQL 兼容原生库中必须严格区分;
- **杜绝硬编码**:不将 `training` 或 `'public'` 字符串硬编码或散落在大量 XML 中;
- **动态环境感知**:利用 Kingbase/PostgreSQL 内置标准函数 `current_schema()`,该函数能够动态求值当前会话首选活动 schema(即返回当前 search_path 的首位 `public`)。
### 6.2 具体修改点
1. **`GenTableMapper.xml`**:
- `selectDbTableList`(待导入表分页查询):
```xml
<!-- 修改前 -->
where table_schema = (select current_database())
<!-- 修改后 -->
where table_schema = (select current_schema())
```
- `selectDbTableListByNames`(批量表查询):
```xml
<!-- 修改前 -->
and table_schema = (select current_database())
<!-- 修改后 -->
and table_schema = (select current_schema())
```
- `selectTableByName`(单表元数据查询):
```xml
<!-- 修改前 -->
and table_schema = (select current_database())
<!-- 修改后 -->
and table_schema = (select current_schema())
```
2. **`GenTableColumnMapper.xml`**:
- `selectDbTableColumnsByName`(表列结构查询):
```xml
<!-- 修改前 -->
from information_schema.columns where table_schema = (select current_database()) and table_name = (#{tableName})
<!-- 修改后 -->
from information_schema.columns where table_schema = (select current_schema()) and table_name = (#{tableName})
```
---
## 7. Generator 验证结果
针对代码生成器执行了纯元数据只读验证,未执行任何物理文件生成,未覆盖项目代码。
### 7.1 表清单检索验证 (`selectDbTableList`)
- **待导入表识别总数**:36 张;
- **模式隔离验证**:全部来源于 `public` schema,未混入 `sys_catalog`、`pg_catalog` 或 `information_schema` 等任何系统内部表;
- **核心表发现结果**:
- `train_teacher`(教师信息表):已识别
- `train_student`(学生信息表):已识别
- `train_course`(课程信息表):已识别
### 7.2 字段与元数据结构读取验证 (`selectDbTableColumnsByName`)
| 目标表名 | 字段总数 | 关键列及类型验证(部分摘要) | 主键识别 | 必填标识 | 注释信息 |
| --- | --- | --- | --- | --- | --- |
| `train_teacher` | 12 | `t_id` (varchar(32)), `t_name` (varchar(30)), `t_department` (varchar(50)), `create_time` (timestamp) | 是 (`t_id`) | 是 (`t_name`等) | 完整保留 |
| `train_student` | 12 | `s_id` (varchar(32)), `s_name` (varchar(30)), `s_department` (varchar(50)), `s_major` (varchar(50)) | 是 (`s_id`) | 是 (`s_name`等) | 完整保留 |
| `train_course` | 14 | `c_id` (varchar(32)), `c_name` (varchar(100)), `teaching_objective` (text), `course_content` (text) | 是 (`c_id`) | 是 (`c_name`等) | 完整保留 |
### 7.3 REST 接口实测 (`GET /tool/gen/db/list`)
通过携带 admin Token 调用后端接口实测:
- `GET /tool/gen/db/list?pageSize=100`:HTTP 200,`total=36`,返回全部 public 业务表;
- `GET /tool/gen/db/list?tableName=train_teacher`:HTTP 200,精确匹配 1 条;
- `GET /tool/gen/db/list?tableName=train_student`:HTTP 200,模糊匹配 3 条(包含 `train_student_checkin`, `train_student_portrait`, `train_student`);
- `GET /tool/gen/db/list?tableName=train_course`:HTTP 200,精确匹配 1 条。
---
## 8. git diff 摘要
本轮变更只涉及基础设施层与代码生成器 XML,完全杜绝业务侵入:
```diff
diff --git a/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml b/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml
--- a/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml
+++ b/RuoYi-Vue/ruoyi-admin/src/main/resources/application-druid.yml
@@ -20,3 +20,4 @@ spring:
- url: jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public&useUnicode=true&characterEncoding=utf8
+ url: jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8
username: kingbase
password: [REDACTED]
+ connectionInitSqls: ["SET search_path TO public, sys_catalog"]
diff --git a/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
--- a/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
+++ b/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
@@ -44,3 +44,3 @@
- from information_schema.columns where table_schema = (select current_database()) and table_name = (#{tableName})
+ from information_schema.columns where table_schema = (select current_schema()) and table_name = (#{tableName})
diff --git a/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
--- a/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
+++ b/RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
@@ -81,3 +81,3 @@
- where table_schema = (select current_database())
+ where table_schema = (select current_schema())
@@ -101,3 +101,3 @@
- where table_name NOT LIKE 'qrtz\_%' and table_name NOT LIKE 'gen\_%' and table_schema = (select current_database())
+ where table_name NOT LIKE 'qrtz\_%' and table_name NOT LIKE 'gen\_%' and table_schema = (select current_schema())
@@ -110,3 +110,3 @@
- where table_comment <![CDATA[ <> ]]> '' and table_schema = (select current_database())
+ where table_comment <![CDATA[ <> ]]> '' and table_schema = (select current_schema())
```
---
## 9. 尚未解决的问题
按照本阶段的原则与禁令,以下历史遗留问题在本轮保持原样,留待后续专项阶段处理:
1. **`sys_role` 记录缺失与孤儿关联**:
- 数据库中 `public.sys_role` 仍然为 0 行记录;
- `sys_user_role` 与 `sys_role_menu` 仍保留指向已缺失 roleId 的孤儿历史数据;
- 普通角色与培训专用角色权限尚未恢复(需在权限模型重构阶段专项建设)。
2. **培训业务表命名与结构规范冲突**:
- 现有 `train_*` 表存在下划线与驼峰命名混用;
- PRD V1.0 要求的“选课管理(Enrollment)”、“排课信息(Lesson/Schedule)”等实体模型在现有数据库中缺失对应表结构;
- 成绩与画像表仍存在与 PRD 边界不符的字段定义。
3. **生成的自增主键(Generated Keys)写链路完整适配**:
- 现有环境已验证驱动声明支持 `useGeneratedKeys`,但写操作(批量 INSERT、事务回滚)的边界行为仍需在数据库 V2 落地时配合单元测试核验。
---
## 10. 是否满足进入数据库 V2 设计的条件
**判定结论**:**完全满足**。
**判定依据**:
1. **基础运行阻断已被清除**:`search_path` 双保险机制消除了 RuoYi 底层 Mapper 与 Kingbase 内置视图的同名冲突,登录、权限与动态路由链路在真实服务环境下完全调通。
2. **元数据逆向工程能力就绪**:代码生成器(Generator)已具备精确读取 Kingbase public schema 下业务表、字段、类型、主键元数据的能力,为后续通过工具链辅助重构与生成实体代码扫清了障碍。
3. **业务边界零污染**:本轮修复未修改任何业务表、未修改任何培训模型、未删除数据库数据、未改动 PRD,代码工作区处于完全整洁受控的状态,符合进入“数据库 V2 设计与模型重构”的准入基线。