Files
railwaysystem/docs/database-v2/13_generator_kingbase_fix_validation.md
T

12 KiB
Raw Blame History

RailwaySystem Generator Kingbase V2 元数据适配修复报告

本文档记录针对上一轮只读验证(12_generator_v2_readonly_validation.md)发现的代码生成器与 KingbaseES 适配风险在工具层的针对性修复与端到端复验结果。 本轮修改严格局限于 Generator 工具层组件与模板,未修改数据库表结构与数据,未导入 Generator 表配置,未生成业务代码。


一、修复全景与最终状态矩阵

风险编号 风险严重级 涉及模块与文件 修复核心动作 复测结果 最终状态
GEN-RISK-BAK-01 低 GenTableMapper.xml 候选表列表及按名称查询增加 table_name NOT LIKE 'bak_v1_%' 过滤 候选表中备份表降为 0,导入入口防御生效 Resolved
GEN-RISK-ID-01 高 GenTableServiceImpl.java 统一通过标准 JDBC DatabaseMetaData.IS_AUTOINCREMENT 动态识别 Identity 12/12 完整识别自增主键,4/4 字符串主键非自增 Resolved
GEN-RISK-DIALECT-01 高 GenTableColumnMapper.xml
GenTableServiceImpl.java
移除 Mapper 中所有 Kingbase 专有 sys_* 系统表 JOIN,恢复通用数据库兼容 通用 Mapper 数据库无关,消除 MySQL 崩溃隐患 Resolved
GEN-RISK-NUMERIC-01 高 GenConstants.java COLUMNTYPE_NUMBER 补充 "numeric",命中 BigDecimal 数值浮点列全量为 BigDecimal Resolved
GEN-RISK-TIME-01 中 domain.java.vm
sub-domain.java.vm
模板区分 date 与 timestamp 格式化模式 DATE/TIMESTAMP 精度完美区分 Resolved
GEN-RISK-STR-01 低 GenConstants.java COLUMNTYPE_STR 补充 "character varying",恢复长度计算 字符串类型 htmlType 正常生成 Resolved

二、六大风险修复明细

2.1 GEN-RISK-BAK-01: 备份表全链路过滤与防御修复

  • 原问题: /tool/gen/db/list 会将数据库中完好保留的 14 张 bak_v1_* 历史快照表一同作为业务候选表展示,存在误选导入风险;且按名称导入接口 selectDbTableListByNames 亦缺乏防绕过保护。
  • 根因: GenTableMapper.xml 中的相关查询原过滤条件仅排除了 qrtz_% 和 gen_%。
  • 修改文件: RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
  • 修复方法: 在 selectDbTableList 与 selectDbTableListByNames 查询中均追加快照过滤条件:
    AND table_name NOT LIKE 'bak\_v1\_%'
    
  • 验证结果:
    • 候选表总数由 57 张降至 43 张(包含 21 张 train_* 正式表及系统表)。
    • bak_v1_* 出现在候选列表: 精确为 0 张。
    • train_score_info 出现在候选列表: 精确为 0 张。
    • 21 张正式 train_* 表: 100% 完整保留。
    • 模拟传入备份表名称调用 selectDbTableListByNames: 被底层 SQL 彻底拦截过滤,无法绕过候选表导入。
  • 状态: Resolved

2.2 GEN-RISK-ID-01 与 GEN-RISK-DIALECT-01: Kingbase Identity 与方言无关架构修复

  • 原问题:
    1. 12 张使用 BIGINT GENERATED BY DEFAULT AS IDENTITY 的业务主键,在 KingbaseES 下因 information_schema.columns.extra 为 null,导致 Generator 判定 is_increment = '0',生成的 MyBatis Mapper XML 丢失 useGeneratedKeys="true"。
    2. 若在 GenTableColumnMapper.xml 中无条件 LEFT JOIN sys_namespace, sys_class, sys_attribute,在连接标准 MySQL 数据库时将因系统目录表不存在而崩溃(GEN-RISK-DIALECT-01)。
  • 根因:
    • 单纯依赖 MySQL 方言特有的 extra = 'auto_increment' 无法捕获 PostgreSQL / Kingbase 的标准 Identity。
    • 但在 MyBatis XML 中硬编码特定数据库专有系统表破坏了跨数据库通用性。
  • 修改文件:
    • RuoYi-Vue/ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
    • RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
  • 修复方法:
    1. 通用 Mapper 彻底解耦: GenTableColumnMapper.xml 恢复为纯标准 SQL,彻底移除任何 sys_* 专有表 JOIN,保留 extra = 'auto_increment':
      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
      
    2. Java 元数据层统一 JDBC 补充与 Fail-Fast 加固: 在 GenTableServiceImpl 中统一封装列查询入口,利用跨数据库通用 JDBC 标准接口 DatabaseMetaData.getColumns 读取 IS_AUTOINCREMENT:
      try (ResultSet rs = meta.getColumns(catalog, schema, tableName, null))
      {
          while (rs.next())
          {
              if ("YES".equalsIgnoreCase(rs.getString("IS_AUTOINCREMENT")))
              {
                  autoCols.add(rs.getString("COLUMN_NAME"));
              }
          }
      }
      
      凡驱动识别出 IS_AUTOINCREMENT = YES,统一设置 column.setIsIncrement("1")。 可靠性加固 (Fail-Closed): 捕获到 SQLException 时记录 ERROR 并主动抛出 ServiceException("读取数据库自增列元数据失败,请检查数据库连接与元数据权限") 中止 Generator 元数据初始化,严禁静默退化导致生成错误的非自增代码。
  • 验证结果:
    • Kingbase 验证 (实测 PASS): 全部 12 张 Identity 业务表的主键(course_resource.id, plan_course.plan_course_id, enrollment.enrollment_id, lesson.lesson_id, accommodation.accommodation_id, dorm_auth.auth_id, class_report.report_id, student_checkin.checkin_id, score_item.score_item_id, student_score.score_id, study_duration.dur_id, student_portrait.portrait_id)经 JDBC 读取 IS_AUTOINCREMENT 均为 "YES",全部正确得到 isPk = 1, isIncrement = 1, javaType = Long;4 张 VARCHAR 主键全部得到 isPk = 1, isIncrement = 0, javaType = String。 Mapper 模板渲染实测输出: <insert ... useGeneratedKeys="true" keyProperty="lessonId">。
    • MySQL 兼容性: 通用 Mapper 已不再引用 Kingbase 专属 sys catalog,原 MySQL auto_increment 逻辑保留,静态兼容性恢复;未进行真实 MySQL 实例执行测试。
  • 状态: Resolved

2.3 GEN-RISK-NUMERIC-01: numeric 类型映射 BigDecimal 修复

  • 原问题: train_score_item.weight, train_score_item.full_score, train_student_score.score 等 numeric(5,2) 和 numeric(7,2) 列被错误识别为 String。
  • 根因: GenConstants.COLUMNTYPE_NUMBER 包含了 decimal 与 number,但未包含标准 SQL 原生类型 "numeric",导致类型匹配失效走默认兜底逻辑。
  • 修改文件: RuoYi-Vue/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
  • 修复方法: 在 COLUMNTYPE_NUMBER 常量数组中追加 "numeric":
    public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
            "bit", "bigint", "float", "double", "decimal", "numeric" };
    
  • 验证结果:
    • train_score_item.weight (numeric(5,2)) -> BigDecimal (PASS)
    • train_score_item.full_score (numeric(5,2)) -> BigDecimal (PASS)
    • train_student_score.score (numeric(7,2)) -> BigDecimal (PASS)
  • 状态: Resolved

2.4 GEN-RISK-STR-01: character varying 字符串类型识别修复

  • 原问题: KingbaseES 原生 VARCHAR 类型名称为 character varying,在 Generator 中虽因兜底映射为 String,但跳过了字符串长度计算分支,导致普通文本列的 htmlType 默认为 null。
  • 根因: GenConstants.COLUMNTYPE_STR 仅声明了 char, varchar, nvarchar, varchar2,未声明 "character varying"。
  • 修改文件: RuoYi-Vue/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
  • 修复方法: 在 COLUMNTYPE_STR 常量数组中追加 "character varying":
    public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2", "character varying" };
    
  • 验证结果:
    • plan_id (character varying(32)): javaType = String, htmlType = input (PASS)
    • plan_name (character varying(150)): javaType = String, htmlType = input (PASS)
    • train_address (character varying(200)): javaType = String, htmlType = input (PASS)
    • remark (character varying(500)): javaType = String, htmlType = textarea (长度>=500分支触发正常) (PASS)
  • 状态: Resolved

2.5 GEN-RISK-TIME-01: DATE 与 TIMESTAMP 格式化精度解耦修复

  • 原问题: 代码生成模板无差别为所有 Date 类型字段生成 @JsonFormat(pattern = "yyyy-MM-dd"),导致起止时间、签到时间、同步时间等时分秒丢失。
  • 根因: domain.java.vm 与 sub-domain.java.vm 仅依据 javaType == 'Date' 做单一规则格式化,未读取 columnType。
  • 修改文件:
    • RuoYi-Vue/ruoyi-generator/src/main/resources/vm/java/domain.java.vm
    • RuoYi-Vue/ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm
  • 修复方法: 在模板中依据 $column.columnType == 'date' 进行条件分支判断:
    #elseif($column.javaType == 'Date')
    #if($column.columnType == 'date')
        @JsonFormat(pattern = "yyyy-MM-dd")
        @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
    #else
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    #end
    #else
        @Excel(name = "${comment}")
    #end
    
  • 验证结果:
    • DATE 字段(t_birth, s_birth)-> 生成 @JsonFormat(pattern = "yyyy-MM-dd") (PASS)
    • TIMESTAMP 字段(start_time, end_time, report_deadline, sign_in_time, sign_out_time, sync_time, create_time, update_time 等)-> 生成 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") (PASS)
    • Velocity 模板真实渲染断言通过。
  • 状态: Resolved

三、安全凭据自查结果

  1. Git 追踪与未追踪区检查: 经严密排查,当前工作区无任何明文真实数据库密码、连接字符串或真实凭据进入 Git 文件与文档。
  2. 测试脚本审查: 临时辅助脚本均隔离存储于工作区之外的 scratch 路径,不纳入版本控制。

四、构建与端到端复验结论

  1. 构建结果: 执行 mvn compile -DskipTests 全模块构建通过(ruoyi-common, ruoyi-generator, ruoyi-admin 依赖链编译成功,0 错误)。
  2. 纯只读保障: 本轮复核未执行任何代码生成,未调用 importTable,gen_table 与 gen_table_column 仍保持 0 记录,数据库结构与数据零改动。

五、最终判定

A. Generator元数据与模板基础能力已适配V2,可进入API/代码架构设计阶段

说明: 本判定表明 RuoYi Generator 在对接 KingbaseES V2 物理模型时的元数据提取与模板映射基础能力已经达到生产可用级别,同时完全恢复了对 MySQL 的通用抽象与无侵入兼容。 特别重申纪律: 最终判定为 A 绝不代表可以立即批量生成 21 套业务代码。下一阶段必须首先开展系统的业务 API Contract 设计、DTO/VO 结构定义、Service 边界划分与模块归属权划分,完成后再按需审慎生成脚手架。