19 KiB
RailwaySystem Database V2 Generator 只读验证报告
本文档记录 RailwaySystem 在数据库 V2 迁移完成后,针对 RuoYi Generator / KingbaseES 元数据查询能力的纯只读验证与审计结果。
本阶段严格遵循只读原则,未修改数据库结构与数据,未导入 Generator 表配置,未向 gen_table/gen_table_column 写入数据,未生成任何 Java/Vue 代码。
1. 验证环境
| 环境项 | 参数 / 状态 | 说明 |
|---|---|---|
| 数据库版本 | KingbaseES V009R003C018 | 官方原生国产数据库 |
| 数据库模式 | database_mode = mysql |
MySQL 兼容模式 |
| 实例名 / Schema | training / public |
当前业务运行上下文 |
| 会话 Search Path | public, sys_catalog |
业务 Schema 优先 |
| JDBC 驱动 | com.kingbase8:kingbase8:8.6.0 |
原生驱动连通 |
| 后端环境 | JDK 17 / Spring Boot 2.5.15 / RuoYi-Vue | 现有工程骨架 |
| 代码生成组件 | ruoyi-generator (Mapper: GenTableMapper, GenTableColumnMapper) |
现有待核验元数据组件 |
2. Generator 元数据入口
Generator 候选数据库表的查询核心位于 GenTableMapper.xml:
- SQL 标识:
selectDbTableList - 实际查询语句:
select table_name, table_comment, create_time, update_time from information_schema.tables where table_schema = (select current_schema()) AND table_name NOT LIKE 'qrtz\_%' AND table_name NOT LIKE 'gen\_%' AND table_name NOT IN (select table_name from gen_table) order by create_time desc
字段级别查询核心位于 GenTableColumnMapper.xml:
- SQL 标识:
selectDbTableColumnsByName - 实际查询语句:
select column_name, (case when (is_nullable = 'no' and column_key != 'PRI') then '1' else '0' end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type from information_schema.columns where table_schema = (select current_schema()) and table_name = (#{tableName}) order by ordinal_position
3. 21 表识别情况
经实测查询,数据库当前精确包含 21 张正式业务表,Generator selectDbTableList 100% 全量识别,无遗漏、无命名歧义:
| 序号 | 表名 | 表注释 | Generator 识别状态 |
|---|---|---|---|
| 1 | train_accommodation |
宿舍学员入住分配表 | PASS (已发现) |
| 2 | train_class_plan |
培训计划表 | PASS (已发现) |
| 3 | train_class_report |
培训开班报告表 | PASS (已发现) |
| 4 | train_classroom_info |
教室信息表 | PASS (已发现) |
| 5 | train_course |
课程信息表 | PASS (已发现) |
| 6 | train_course_resource |
课程与教学资源关联表 | PASS (已发现) |
| 7 | train_dorm_auth |
电子门禁授权表 | PASS (已发现) |
| 8 | train_dormitory_info |
宿舍信息表 | PASS (已发现) |
| 9 | train_electronic_board |
电子牌信息表 | PASS (已发现) |
| 10 | train_enrollment |
培训班学员参训档案表 | PASS (已发现) |
| 11 | train_lesson |
排课课次日程表 | PASS (已发现) |
| 12 | train_plan_course |
培训班关联课程表 | PASS (已发现) |
| 13 | train_resource |
教学资源信息表 | PASS (已发现) |
| 14 | train_score_item |
考核与成绩项配置表 | PASS (已发现) |
| 15 | train_student |
学员基础信息表 | PASS (已发现) |
| 16 | train_student_checkin |
学员课次考勤签到记录表 | PASS (已发现) |
| 17 | train_student_portrait |
学员学习画像表 | PASS (已发现) |
| 18 | train_student_score |
学员单项成绩明细表 | PASS (已发现) |
| 19 | train_study_duration |
学员多端学习时长流水表 | PASS (已发现) |
| 20 | train_teacher |
教师基础信息表 | PASS (已发现) |
| 21 | train_training_site |
实训基地信息表 | PASS (已发现) |
- 废弃表下线确认: 数据库中
train_score_info计数为 0,Generator 候选列表中完全不存在该表。 - 系统对象隔离确认: 未混入 Kingbase 系统表(
sys_catalog过滤正常)。
4. bak_v1_* 污染情况
- 实测现状:
数据库中完好保留的 14 张
bak_v1_*快照表全部出现在 Generator 的/tool/gen/db/list候选表列表中:bak_v1_train_class_planbak_v1_train_class_reportbak_v1_train_classroom_infobak_v1_train_coursebak_v1_train_dorm_authbak_v1_train_dormitory_infobak_v1_train_electronic_boardbak_v1_train_score_infobak_v1_train_studentbak_v1_train_student_checkinbak_v1_train_student_portraitbak_v1_train_study_durationbak_v1_train_teacherbak_v1_train_training_site
- 风险登记:
GEN-RISK-BAK-01(Generator 当前会将数据库备份表识别为待生成表,存在前端误勾选导入的风险)。 - 根因分析:
GenTableMapper.xml中的selectDbTableList仅排除了qrtz\_%和gen\_%,未针对备份表前缀做过滤。 - 治理建议方案:
- 方案 A (推荐): 在 Generator 查询层
GenTableMapper.xml的selectDbTableList中增加AND table_name NOT LIKE 'bak\_%'进行只读过滤。 - 方案 B: 保持现状,待后续业务全面平稳上线、备份表归档下线后自然消失。
- 最终决定由技术负责人审定,本轮不修改代码。
- 方案 A (推荐): 在 Generator 查询层
5. PK 识别
对于字符串主键(VARCHAR):
- 代表性字段:
train_teacher.t_idtrain_student.s_idtrain_course.c_idtrain_class_plan.plan_id
- 实测结果:
information_schema.columns.column_key正确输出'PRI'。- Generator 的
case when column_key = 'PRI' then '1' else '0' end正确计算出is_pk = '1'。 - 类型识别为
character varying(32)-> Java 类型String。 is_increment = '0'。
- 结论: PASS (VARCHAR 主键识别完全正确,非自增属性准确)。
6. Identity 识别
对于大整数自增主键(BIGINT GENERATED BY DEFAULT AS IDENTITY):
- 代表性字段:
train_plan_course.plan_course_idtrain_enrollment.enrollment_idtrain_lesson.lesson_idtrain_accommodation.accommodation_idtrain_student_checkin.checkin_idtrain_score_item.score_item_idtrain_student_score.score_idtrain_study_duration.dur_id
- 实测结果:
column_key = 'PRI',is_pk = '1'(主键属性识别正确)。column_type = bigint,Java 类型为Long(主键类型识别正确)。- 异常:
is_increment被识别为0(未识别出自增/Identity)!
- 根因分析:
- KingbaseES 底层系统目录中
sys_attribute.attidentity = 'd',但在兼容视图information_schema.columns中,其extra字段返回为null,而未返回 MySQL 风格的'auto_increment'。 GenTableColumnMapper.xml中使用(case when extra = 'auto_increment' then '1' else '0' end) as is_increment,直接导致is_increment输出为'0'。
- KingbaseES 底层系统目录中
- MyBatis 影响评估:
mapper.xml.vm中控制自增主键的核心逻辑为:<insert id="insert${ClassName}" parameterType="${ClassName}"#if($pkColumn.increment) useGeneratedKeys="true" keyProperty="$pkColumn.javaField"#end>- 若
is_increment为'0',生成的 Mapper XML 将丢失useGeneratedKeys="true",且主键列将被列入 insert 列列表中。插入数据时无法自动回填主键 ID,将直接破坏后续业务插入。
- 风险登记:
GEN-RISK-ID-01(Identity 主键未能识别为自增,导致 MyBatis 无法自动开启useGeneratedKeys)。
7. Java 类型映射
对各类数据类型的映射实测结果汇总:
| 数据库类型 | 典型代表字段 | Generator 映射 Java 类型 | 预期类型 | 判定 | 风险登记 |
|---|---|---|---|---|---|
BIGINT |
enrollment_id, user_id |
Long |
Long |
PASS | 无 |
CHARACTER VARYING(N) |
plan_id, t_name |
String |
String |
PASS* | 见说明* |
TEXT |
apply_condition, id_card_ciphertext |
String |
String |
PASS | 无 (htmlType 为 textarea) |
TIMESTAMP |
start_time, create_time |
Date |
Date |
PASS | 存在时间精度隐患(见第9节) |
DATE |
s_birth, t_birth |
Date |
Date |
PASS | 无 |
INTEGER |
recruit_num, study_seconds |
Long |
Integer / Long |
PASS | 无括号走兜底 Long |
SMALLINT |
status, source_type |
Long |
Integer / Long |
PASS | 无括号走兜底 Long |
NUMERIC(5,2) |
train_score_item.weight |
String |
BigDecimal |
FAIL | GEN-RISK-NUMERIC-01 |
NUMERIC(7,2) |
train_student_score.score |
String |
BigDecimal |
FAIL | GEN-RISK-NUMERIC-01 |
* 说明 (
GEN-RISK-STR-01):GenConstants.COLUMNTYPE_STR中仅包含{"char", "varchar", "nvarchar", "varchar2"},未包含 Kingbase 原生类型名"character varying"。虽然由于兜底逻辑最终 JavaType 正确为String,但跳过了字符串长度判断,导致htmlType默认为null。致命缺陷 (
GEN-RISK-NUMERIC-01):GenConstants.COLUMNTYPE_NUMBER中包含decimal和number,但遗漏了标准 SQL 的numeric! 导致numeric(5,2)和numeric(7,2)无法命中数字处理逻辑,直接落入兜底的String,导致金额/权重/分数等字段被错误生成为String类型,后续计算无法进行。
8. BaseEntity 字段识别
核验 6 个通用审计字段在 21 表中的表现:
| 数据库字段 | Generator 生成 JavaField | 识别 JavaType | BaseEntity 兼容性 | 备注 |
|---|---|---|---|---|
create_by |
createBy |
String |
PASS | 继承自 BaseEntity,非编辑/非查询 |
create_time |
createTime |
Date |
PASS | 继承自 BaseEntity,非编辑 |
update_by |
updateBy |
String |
PASS | 继承自 BaseEntity,非编辑/非列表 |
update_time |
updateTime |
Date |
PASS | 继承自 BaseEntity,非编辑/非列表 |
remark |
remark |
String |
PASS | 继承自 BaseEntity,非查询 |
del_flag |
delFlag |
String |
PASS | 仅主数据表包含,非编辑/非列表/非查询 |
- 旧代码习惯排查: 21 表中彻底不存在旧代码习惯的
create_user字段,全部统一为create_by。 - 实体继承结论: Generator 可以无缝继承 RuoYi 标准的
BaseEntity。
9. 时间字段风险
在 V2 架构中,存在大量需要秒级/毫秒级精度的时间字段:
-
start_time/end_time(排课起止时间、培训班起止时间) -
report_deadline(开班报到截止时间) -
join_time/complete_time(参训档案进入/结业时间) -
assigned_time(宿舍分配时间) -
sign_in_time/sign_out_time(考勤打卡签到签退时间) -
sync_time(多端学习流水同步时间) -
import_time(外部成绩导入时间) -
实测代码生成模版隐患: 审查
domain.java.vm与sub-domain.java.vm第 42~45 行:#elseif($column.javaType == 'Date') @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") -
风险分析: 模板中对所有
Date类型字段一律硬编码为@JsonFormat(pattern = "yyyy-MM-dd"),未根据字段名或时间类型区分是否包含时分秒。若直接使用该模板生成代码,上述高精时间在前后端 JSON 交互时时分秒将被强制截断舍弃! -
风险登记:
GEN-RISK-TIME-01。
10. 代表性 11 张表字段核验
对 11 张核心领域代表性表执行端到端元数据字段识别核验:
10.1 train_teacher (主数据-教师)
t_id: String, PK (is_pk=1, is_inc=0)t_name,t_phone,t_email,org_name,job_title: Stringt_birth: Date (datetime)status: Long (radio)del_flag: String- 核验结论: 字段完全对齐 V2,旧字段清理彻底。
10.2 train_student (主数据-学员)
s_id: String, PK (is_pk=1, is_inc=0)id_card_ciphertext: String (textarea)id_card_hash,id_card_last4,face_ref: Stringsource_type: Long (select)status: Long (radio)del_flag: String- 核验结论: 脱敏与加密字段均正确识别为 String。
10.3 train_class_plan (主数据-培训班)
plan_id: String, PK (is_pk=1, is_inc=0)start_time,end_time,report_deadline: Daterecruit_num: Longapply_condition: String (textarea)plan_status: Long (radio)del_flag: String- 核验结论: 完全对齐 V2 培训班模型。
10.4 train_plan_course (关联表-班级课程)
plan_course_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)plan_id,c_id: Stringsort_no: Long- 核验结论: 班级与课程多对多关联实体,结构精确。
10.5 train_enrollment (核心域-参训档案)
enrollment_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)plan_id,student_id: Stringsource_type: Long (select)enrollment_status: Long (radio)accommodation_required: Longjoin_time,complete_time: Date- 核验结论: 彻底替代 V1 学员班级直接冗余,模型清晰。
10.6 train_lesson (核心域-课次日程)
lesson_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)plan_course_id: Long (FK)t_id: String (FK)start_time,end_time: Datevenue_type: Long (select)class_room_id,site_id: String- 核验结论: 排课三维时空冲突字段全部就绪。
10.7 train_accommodation (保障域-住宿分配)
accommodation_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)enrollment_id: Long (FK -> train_enrollment)dorm_id,bed_no: Stringassigned_time: Dateaccommodation_status: Long (radio, 默认 0)- 核验结论: 外键正确挂接档案 ID。
10.8 train_student_checkin (考勤域-课次打卡)
checkin_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)enrollment_id: Long (FK)lesson_id: Long (FK)board_id: Stringcheckin_way: Longsign_in_time,sign_out_time: Dateattendance_status: Long (radio)manual_flag: Long- 核验结论: 双外键挂接档案与课次,完全符合打卡流。
10.9 train_score_item (考核域-考核项目)
score_item_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)plan_id: Stringplan_course_id: Long (可空)item_name: Stringitem_type: Long (select)weight,full_score: String (触发 GEN-RISK-NUMERIC-01 错误映射)sort_no: Long- 核验结论: 结构完整,暴露出 numeric 映射缺陷。
10.10 train_student_score (考核域-学员成绩明细)
score_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)enrollment_id: Long (FK)score_item_id: Long (FK)score: String (触发 GEN-RISK-NUMERIC-01 错误映射)source_type: Long (select)source_system,source_event_id: Stringimport_time: Date- 核验结论: 彻底摆脱 V1
train_score_info冗余模型。
10.11 train_study_duration (学时域-多端学习时长流水)
dur_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)enrollment_id: Long (FK)plan_course_id: Longresource_id: Stringlearning_content: String (editor)source_system,source_event_id: Stringstart_time,end_time: Datestudy_seconds: Longsync_time: Date- 核验结论: 幂等键与档案外键准确。
11. 旧 Generator 配置状态
- 实测查询:
SELECT count(*) FROM gen_table; -- 结果: 0 SELECT count(*) FROM gen_table_column; -- 结果: 0 - 审计结论:
数据库中没有任何旧系统的
gen_table/gen_table_column残留记录,处于 100% 洁净状态,不存在历史配置覆盖与失配干扰。
12. 发现的问题清单 (GEN-RISK)
| 风险编号 | 严重等级 | 涉及模块 | 问题现象与根因 | 建议修复对策 |
|---|---|---|---|---|
GEN-RISK-BAK-01 |
低 | GenTableMapper.xml |
selectDbTableList 未过滤 bak_v1_*,导致 14 张备份快照表出现在候选列表 |
在 Mapper 查询条件中追加 AND table_name NOT LIKE 'bak\_%' |
GEN-RISK-ID-01 |
高 | GenTableColumnMapper.xml |
KingbaseES 的 identity 列在 info_schema 中 extra 为 null,导致 is_increment 被判为 0,生成的 MyBatis XML 将缺少 useGeneratedKeys="true" |
优化 is_increment 判断逻辑,通过系统目录兼容 Kingbase identity |
GEN-RISK-NUMERIC-01 |
高 | GenConstants.java |
常量库中缺少 "numeric" 类型,导致 numeric(5,2) 和 numeric(7,2) 被误映射为 String |
在 COLUMNTYPE_NUMBER 中补充 "numeric",以映射为 BigDecimal |
GEN-RISK-TIME-01 |
中 | domain.java.vm |
代码生成模板无差别为所有 Date 生成 @JsonFormat(pattern = "yyyy-MM-dd"),导致秒级/时间点字段丢失时分秒 |
针对 start_time / create_time 等精确时间采用 yyyy-MM-dd HH:mm:ss |
GEN-RISK-STR-01 |
低 | GenConstants.java |
常量库中缺少 "character varying",导致 varchar 字段未进入长度计算,htmlType 置空 |
在 COLUMNTYPE_STR 中补充 "character varying" |
13. 最终判定
根据上述严谨测试与风险评估,最终判定结论为:
B. Generator需要小幅元数据/模板修复后再使用
判定理由:
- 不可选 A: Generator 在识别数字类型(
numeric漏判导致String)、自增主键(is_increment=0导致无useGeneratedKeys)以及时间格式(时分秒丢失)存在 3 项明确的阻断性缺陷。若直接用于生成代码,将导致编译期类型错乱与运行时主键丢失。- 不可选 C: KingbaseES 数据库连接、21 表发现、字段提取、字段注释、主键 PRI、BaseEntity 兼容性均 100% 正常,数据通路完全畅通,不存在不可克服的底层架构障碍。
- 判定为 B 的充分性: 仅需对 Generator 进行小幅元数据映射补齐(增加
numeric与character varying常量、兼容 identity 识别、优化时间模板格式),即可达到工业级稳定代码生成要求。
Note
后续行动指引: 本轮严格执行只读与不生成代码纪律。下一阶段应首先由技术负责人开展 API 契约、DTO/VO、Service 边界设计;在正式使用 Generator 生成基础骨架前,集中对上述 B 类问题进行统一微调修复。