Files
railwaysystem/docs/database-v2/12_generator_v2_readonly_validation.md
T

19 KiB
Raw Blame History

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_plan
    • bak_v1_train_class_report
    • bak_v1_train_classroom_info
    • bak_v1_train_course
    • bak_v1_train_dorm_auth
    • bak_v1_train_dormitory_info
    • bak_v1_train_electronic_board
    • bak_v1_train_score_info
    • bak_v1_train_student
    • bak_v1_train_student_checkin
    • bak_v1_train_student_portrait
    • bak_v1_train_study_duration
    • bak_v1_train_teacher
    • bak_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: 保持现状,待后续业务全面平稳上线、备份表归档下线后自然消失。
    • 最终决定由技术负责人审定,本轮不修改代码。

5. PK 识别

对于字符串主键(VARCHAR):

  • 代表性字段:
    • train_teacher.t_id
    • train_student.s_id
    • train_course.c_id
    • train_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_id
    • train_enrollment.enrollment_id
    • train_lesson.lesson_id
    • train_accommodation.accommodation_id
    • train_student_checkin.checkin_id
    • train_score_item.score_item_id
    • train_student_score.score_id
    • train_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'。
  • 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: String
  • t_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: String
  • source_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: Date
  • recruit_num: Long
  • apply_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: String
  • sort_no: Long
  • 核验结论: 班级与课程多对多关联实体,结构精确。

10.5 train_enrollment (核心域-参训档案)

  • enrollment_id: Long, PK (is_pk=1, is_inc=0 触发 GEN-RISK-ID-01)
  • plan_id, student_id: String
  • source_type: Long (select)
  • enrollment_status: Long (radio)
  • accommodation_required: Long
  • join_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: Date
  • venue_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: String
  • assigned_time: Date
  • accommodation_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: String
  • checkin_way: Long
  • sign_in_time, sign_out_time: Date
  • attendance_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: String
  • plan_course_id: Long (可空)
  • item_name: String
  • item_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: String
  • import_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: Long
  • resource_id: String
  • learning_content: String (editor)
  • source_system, source_event_id: String
  • start_time, end_time: Date
  • study_seconds: Long
  • sync_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需要小幅元数据/模板修复后再使用

判定理由:

  1. 不可选 A: Generator 在识别数字类型(numeric 漏判导致 String)、自增主键(is_increment=0 导致无 useGeneratedKeys)以及时间格式(时分秒丢失)存在 3 项明确的阻断性缺陷。若直接用于生成代码,将导致编译期类型错乱与运行时主键丢失。
  2. 不可选 C: KingbaseES 数据库连接、21 表发现、字段提取、字段注释、主键 PRI、BaseEntity 兼容性均 100% 正常,数据通路完全畅通,不存在不可克服的底层架构障碍。
  3. 判定为 B 的充分性: 仅需对 Generator 进行小幅元数据映射补齐(增加 numeric 与 character varying 常量、兼容 identity 识别、优化时间模板格式),即可达到工业级稳定代码生成要求。

Note

后续行动指引: 本轮严格执行只读与不生成代码纪律。下一阶段应首先由技术负责人开展 API 契约、DTO/VO、Service 边界设计;在正式使用 Generator 生成基础骨架前,集中对上述 B 类问题进行统一微调修复。