docs(审计): 输出Phase 0.5基础运行修复与端到端验证审计报告

- 发布 docs/codebase-audit/18_phase05_runtime_fix.md,完整记录 search_path 实验结果、数据源双保险配置方案、登录链验证数据及代码生成器元数据验证
- 在 docs/README.md 核心综述清单中收录 18_phase05_runtime_fix.md 并更新审计报告索引至 19 篇
This commit is contained in:
wangzhibiao committed 2026-09-14 00:56:18 +08:00
1 parent 0fd4a66d81
commit 4b450f46af
2 files changed
+254 -1

No files matched your search

@@ -0,0 +1,252 @@
# 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 设计与模型重构”的准入基线。