系列文章第 6 篇 | 基础设施篇 · 代码生成器(yudao-module-infra codegen)

大家好,我是你们的 RuoYi-Vue-Pro 源码拆解系列作者。前几篇文章我们搞定了框架层、认证权限、用户组织、多租户、字典通知这些模块,今天终于来到了我个人认为整个项目最"硬核"的模块——代码生成器。为什么这么说?因为这个模块动辄 101 个 Velocity 模板文件、支持 10 种前端框架、覆盖单表/树表/主子表三种模式,堪称企业级 CRUD 的"印钞机"。废话不多说,直接上干货!


一、今日模块概览

一句话说明白:代码生成器(codegen)解决的是"重复劳动"问题

在企业级后台开发中,80% 的工作其实是 CRUD——建表、写 Controller、写 Service、写 Mapper、写 Vue 页面、写 API 接口……每张表都要来一遍,换个表名再来一遍,换个项目又来一遍。代码生成器的作用就是:你只需要建好数据库表,点一下按钮,前后端代码全部自动生成,直接复制粘贴到项目里就能跑。

芋道的 codegen 模块放在 yudao-module-infra(基础设施模块)里,属于"一次配置、反复使用"的基础工具。它的核心工作流是:

  1. 从数据库读取表结构(表名、字段名、字段类型、注释等)
  2. 在管理后台配置生成规则(模块名、业务名、类名、前端框架等)
  3. 基于 Velocity 模板引擎渲染出完整的 Java + Vue 代码
  4. 支持在线预览或 ZIP 下载

二、技术选型分析

2.1 模板引擎:为什么选 Velocity 而不是 Freemarker / Thymeleaf?

这是很多人会问的问题。Java 生态里模板引擎不少,我们逐个看:

模板引擎 优势 劣势 适合场景
Velocity 语法简洁、学习成本低、Hutool 有统一抽象 社区活跃度下降,Apache 已停止重大更新 代码生成、邮件模板
Freemarker 功能强大、指令丰富、OpenOffice 也在用 语法较复杂,<#if> <#list> 嵌套多了容易晕 报表生成、静态页面
Thymeleaf Spring Boot 官方推荐、支持自然模板 主要面向 HTML 渲染,做代码生成不太顺手 Web 页面渲染
Jinja2 / Mustache 跨语言、前端同学友好 Java 生态支持弱,需要额外引入库 多语言项目

划重点!!! 芋道选择 Velocity 的关键原因:Hutool 的 hutool-extra 模块提供了 TemplateEngine 抽象层,底层用 Velocity 实现,但如果将来想换引擎,只需要改一行配置。这是一个很聪明的"留后路"设计。

另外,Velocity 的语法对代码生成场景特别友好——$!{variable} 空值安全输出、#foreach 循环、#if 条件判断,写起来比 Freemarker 简洁不少。你可以对比一下:

## Velocity 写法
#if(${column.listOperation})
    @ApiModelProperty(value = "${column.columnComment}")
    private ${column.javaType} ${column.javaField};
#end
<#-- Freemarker 写法 -->
<#if column.listOperation>
    @ApiModelProperty(value = "${column.columnComment}")
    private ${column.javaType} ${column.javaField};
</#if>

差距不大,但 Velocity 的 # 前缀指令在大量模板嵌套时可读性更好。

2.2 数据库元数据读取:为什么用 MyBatis-Plus Generator 而不是直接读 JDBC DatabaseMetaData?

这是一个很有意思的技术决策。MyBatis-Plus Generator(mybatis-plus-generator)本身是一个代码生成器,但芋道只用了它的数据库元数据解析能力,没有用它自带的代码生成功能。

为什么不直接用 JDBC 的 DatabaseMetaData?因为不同数据库(MySQL、PostgreSQL、Oracle、SQL Server、达梦、人大金仓……)的元数据返回格式差异很大,处理起来极其繁琐。MyBatis-Plus Generator 内部已经把这些差异抹平了,返回统一的 TableInfo 和 TableField 对象。

// 芋道的做法:用 MyBatis-Plus Generator 读取表结构
ConfigBuilder configBuilder = new ConfigBuilder(null, dataSourceConfig,
    strategyConfig, null, globalConfig, null);
List<TableInfo> tableInfoList = configBuilder.getTableInfoList();
// 每个 TableInfo 包含表名、注释、字段列表,字段列表里每个 TableField 包含
// 列名、JDBC 类型、Java 类型、是否主键、是否可空、注释等信息

踩坑提醒: 这里有个细节——芋道在构建 StrategyConfig 时,显式排除了工作流表(ACT_*、QRTZ_*、FLW_*)和 Oracle 系统表(IMPDP_*、ALL_*),避免这些系统表被误导入代码生成器。如果你自己实现类似功能,千万别忘了做这个过滤。

2.3 对象转换:MapStruct + BeanUtils 双剑合璧

芋道在 codegen 模块中同时用了两种对象转换方式:

  • MapStruct(CodegenConvert):用于 TableInfo → CodegenTableDO 这种跨库对象的转换,编译期生成转换代码,性能最优
  • BeanUtils.toBean:用于 CodegenTableDO → CodegenTableRespVO 这种同项目内 DO→VO 的转换,写法更简洁

这种"混合双打"的策略在芋道项目中很常见:跨层用 MapStruct,同层用 BeanUtils,既保证性能又不牺牲开发效率。


三、需求溯源推演

让我们倒回去想想,这个模块最初的需求是怎么来的?

3.1 场景还原

假设你是芋道源码的作者"芋道源码",2019 年左右开始做这个项目。你面对的现实是:

  • 企业客户找你做后台管理系统,一个项目少说 30-50 张表
  • 每张表的标准 CRUD 代码结构几乎一样:Controller → Service → Mapper → DO → Vue 页面
  • 手写一张表大概要 2-3 小时,50 张表就是 100-150 小时纯重复劳动
  • 客户不愿意为"复制粘贴"买单,但你又必须保证代码质量一致

最初的需求文档可能是这样的:

需求标题: 代码自动生成工具

用户故事: 作为后端开发者,我希望在管理后台选择数据库表后,能自动生成标准的 CRUD 代码(Java 后端 + Vue 前端),这样我可以把时间花在复杂业务逻辑上,而不是重复的增删改查。

核心功能:

  1. 能读取数据库的所有表结构
  2. 可以配置每张表的模块名、类名等信息
  3. 一键生成前后端代码,支持下载 ZIP
  4. 生成前可以在线预览

验收标准: 生成的代码可以直接放入项目运行,不需要手动修改包名、路径等。

3.2 需求演进

但实际需求远不止"生成 CRUD"这么简单。随着项目发展,需求不断膨胀:

  • V1.0:只支持单表 CRUD 生成(templateType = ONE)
  • V2.0:加入树表支持(templateType = TREE),因为部门管理、菜单管理都是树形结构
  • V3.0:加入主子表支持(MASTER_NORMAL / MASTER_ERP / MASTER_INNER),因为订单-订单项、合同-合同明细这种场景太常见了
  • V4.0:前端模板从 Vue2 Element UI 扩展到 Vue3 Element Plus、Vben2、Vben5(三套 UI 库 × Schema/General 两种模式)、UniApp 移动端
  • V5.0:加入 Excel 导入导出、单元测试、多数据源支持

从代码里可以看到,前端模板类型枚举 CodegenFrontTypeEnum 已经有 10 个值,这意味着同一个后端表结构可以生成 10 种不同前端风格的代码。这种演进速度在开源项目中是相当快的。


四、竞品对标分析

代码生成器是 Java 后台管理系统的"标配功能",我们来看看主流竞品是怎么做的:

对比维度 RuoYi-Vue-Pro JeecgBoot Pig Guns SpringBlade
模板引擎 Velocity(Hutool 抽象) Freemarker + 自定义 Online 表单 无内置 codegen BeetlSQL 自带 Beetl 模板
前端支持 10 种(Vue2/Vue3/Vben5/UniApp) Vue3 Ant Design Vue3 Element Plus Vue3 + React Vue3
表结构同步 支持从 DB 同步变更 支持 不支持 不支持 支持
主子表 3 种模式(标准/ERP/内嵌) 支持 不支持 不支持 不支持
树表 支持 支持 不支持 不支持 不支持
Online 表单 不支持(纯代码生成) 支持(在线配置表单布局) 不支持 支持 不支持
多数据源 支持 支持 不支持 不支持 支持
代码可下载 ZIP 下载 ZIP 下载 直接写入项目 直接写入项目 ZIP 下载

4.1 RuoYi-Vue-Pro 的优势

前端模板覆盖最广:10 种前端模板类型,从 Vue2 到 Vue3,从 Element Plus 到 Ant Design,从 PC 端到 UniApp 移动端,几乎覆盖了市面上所有主流的前端技术栈。这对于技术选型多样化的企业团队特别友好。

主子表三种模式:标准模式(弹窗展示子表)、ERP 模式(Tab 页签展示)、内嵌模式(子表嵌入主表表单),覆盖了企业常见的三种主子表交互形态。竞品中只有 JeecgBoot 有类似能力。

DB 同步机制:数据库表结构变更后,可以一键同步到代码生成器,自动识别新增/修改/删除的字段,不需要重新导入。这个功能在实际开发中非常实用。

4.2 RuoYi-Vue-Pro 的劣势

缺少 Online 表单设计器:JeecgBoot 的 Online 表单允许用户通过拖拽方式设计表单布局,对于不会写代码的业务人员也能配置简单页面。芋道走的是"生成源码 → 二次开发"的路线,门槛稍高。

模板维护成本高:101 个 .vm 模板文件,每增加一种前端框架就要新增一整套模板。如果底层框架升级(比如 Vben5 改了 API),模板也要跟着改。这是一个隐性的维护负担。


五、核心业务流程

5.1 完整工作流

让我们用文字 + 流程图来描述 codegen 的完整链路:

5.2 智能默认值推断(CodegenBuilder)

这是 codegen 模块里我觉得最有趣的设计之一。CodegenBuilder 会根据字段名的后缀,自动推断这个字段在页面上应该怎么展示、在查询时应该用什么条件。

查询条件推断规则:

字段名后缀 推断的查询条件 举例
*name LIKE(模糊匹配) user_name、dept_name
*time / *date BETWEEN(范围查询) create_time、update_date
其他 EQ(精确匹配) status、type

UI 控件推断规则:

字段名后缀 / 类型 推断的 UI 控件 举例
*status / *sex 单选按钮(Radio) user_status、sex
*type 下拉选择(Select) order_type
*image 图片上传 avatar_image
*file 文件上传 attachment_file
*content / *description 富文本编辑器 article_content
*time / *date 日期时间选择器 create_time
Boolean 类型 单选按钮 is_deleted
LocalDateTime 类型 日期时间选择器 时间类型字段
其他 文本输入框 大部分字段

Swagger 示例值生成:

// 根据字段名后缀生成合理的示例值
private static void processColumnExample(CodegenColumnDO column) {
    // *id → 随机数字
    // *price → 随机金额
    // *name → 随机中文姓名
    // *url → "https://www.iocoder.cn"
    // 其他 → 根据 Java 类型生成默认值
}

这个设计思路非常值得借鉴。它本质上是一种基于约定的智能推断——只要你的字段命名遵循一定规范(这也是阿里 Java 开发手册推荐的),生成器就能给出 80% 正确的配置,剩下 20% 手动微调即可。


六、数据模型解读

6.1 核心表结构

codegen 模块只用了两张表,设计非常精炼:

infra_codegen_table(代码生成表定义)——存储"哪张表要生成什么代码":

字段 类型 说明 设计意图
data_source_config_id bigint 数据源编号 支持多数据源,不只是主库
table_name varchar(200) 数据库表名 如 system_dept
module_name varchar(30) 模块名 从表名自动解析,如 system
business_name varchar(30) 业务名 从表名自动解析,如 dept
class_name varchar(100) 类名 如 Dept
template_type tinyint 模板类型 1=单表, 2=树表, 10/11/12=主子表, 15=子表
front_type tinyint 前端类型 10=Vue2, 20=Vue3, 40=Vben5...
master_table_id bigint 主表编号 主子表场景下,子表指向主表
sub_join_column_id bigint 关联字段编号 子表通过哪个字段关联主表
tree_parent_column_id bigint 父字段编号 树表场景下,哪个字段是 parentId

infra_codegen_column(代码生成字段定义)——存储"每个字段怎么生成代码":

字段 类型 说明 设计意图
table_id bigint 所属表编号 关联 infra_codegen_table
column_name varchar(200) 数据库字段名 如 dept_name
java_type varchar(32) Java 类型 如 String、Long
java_field varchar(64) Java 属性名 如 deptName(驼峰转换)
create_operation bit 是否参与创建 控制 SaveReqVO 里有哪些字段
update_operation bit 是否参与更新 控制 SaveReqVO 的更新模式
list_operation bit 是否参与列表查询 控制 PageReqVO 里有哪些查询条件
list_operation_condition varchar(32) 查询条件类型 =、LIKE、BETWEEN 等
html_type varchar(32) 前端控件类型 input、select、radio 等
dict_type varchar(200) 关联字典类型 绑定系统字典,前端自动渲染选项

6.2 表关系设计

设计亮点: 两张表就搞定了代码生成的全部元数据。infra_codegen_table 通过 master_table_id 自关联实现主子表关系,通过 tree_parent_column_id 实现树表关系,不需要额外的关联表。这种"一表多用"的设计减少了表数量,也降低了理解成本。

特别注意: 两张表都加了 @TenantIgnore 注解,意味着代码生成器是跨租户的全局功能。这很合理——代码生成是开发阶段的行为,跟运行时租户隔离无关。


七、产品设计亮点与槽点

7.1 让我眼前一亮的设计

1. "后缀推断"的智能默认值

前面已经详细讲了 CodegenBuilder 的后缀匹配机制。这个设计的精妙之处在于:它不需要任何 AI 或机器学习,纯粹靠约定优于配置的思路,就能给出 80% 准确率的默认配置。这背后是对大量企业 CRUD 场景的经验沉淀。

2. 主子表三种模式

  • 标准模式(MASTER_NORMAL):子表以弹窗形式展示,适合"主表详情里看子表列表"的场景
  • ERP 模式(MASTER_ERP):主表和子表以 Tab 页签切换,适合"订单头-订单行"这种 ERP 经典场景
  • 内嵌模式(MASTER_INNER):子表直接嵌入主表表单页面,适合"子表数据量小、需要和主表一起提交"的场景

三种模式对应三套完全不同的前端模板(form_sub_normal.vue.vm、form_sub_erp.vue.vm、form_sub_inner.vue.vm),这种细粒度的模板拆分在竞品中是独一份的。

3. Jakarta / Cloud 双模式兼容

CodegenEngine 在构造时自动检测运行环境:

// 检测是否支持 Jakarta(Spring Boot 3.x / JDK 17+)
jakartaEnable = SystemUtil.getJavaInfo().getVersionFloat() >= 17
        && ClassUtils.isPresent("jakarta.annotation.Resource", null);

// 检测是否是 Cloud 微服务模式
cloudEnable = ClassUtils.isPresent(
        "cn.iocoder.yudao.framework.rpc.config.RpcConfiguration", null);

如果是 Spring Boot 2.x,生成的代码用 javax.annotation.Resource;如果是 3.x,用 jakarta.annotation.Resource。如果是单体模式,生成的 Java 文件路径不包含模块前缀;如果是微服务模式,路径中会加上 yudao-module-xxx 前缀。一套模板,两种环境自适应,这个设计很优雅。

4. prettyCode() 后处理

生成的代码不是简单地渲染模板就完事了,还有一个后处理步骤:

private String prettyCode(String code, String templatePath) {
    // 1. 去除 Vue 对象末尾多余的逗号(避免 IE 报错)
    // 2. 去除未使用的 dateFormatter 导入
    // 3. 修复 Vue2 的 $refs 引用
    // 4. 去除未使用的字典选项导入(getIntDictOptions 等)
    // 5. 去除未使用的 DICT_TYPE 导入
}

这种"生成后自动清理"的思路,说明作者非常了解实际使用中的痛点——生成的代码如果有无用的 import,虽然不影响运行,但看着不舒服,每次都要手动删。

7.2 我觉得可以改进的地方

1. 模板文件缺乏版本管理

101 个 .vm 模板文件散落在 resources/codegen/ 目录下,没有版本号。如果未来要做模板升级(比如从 Element Plus 2.x 升到 3.x),很难做版本对比和回滚。建议给模板加版本号或者用 Git 子模块管理。

2. 缺少"生成到项目目录"的能力

目前 codegen 只支持"预览 + ZIP 下载",开发者需要手动把代码复制到对应目录。如果能支持配置"输出根目录",一键生成到项目的 src/main/java/ 下,会更方便。当然,这可能是故意的设计——避免误覆盖已有代码。

3. 同步机制可以更智能

当前的 syncCodegenFromDB 只同步字段的"结构变更"(类型、可空、主键、注释),但不会自动调整该字段的 createOperation、htmlType 等配置。比如你新增了一个 order_status 字段,同步后它的 htmlType 默认是 input,但你可能希望是 radio。如果同步时能复用 CodegenBuilder 的后缀推断逻辑,体验会更好。

4. 前端模板的 Schema 和 General 模式差异不够清晰

Vben5 的模板分了 Schema(用 JSON Schema 描述表单)和 General(直接写 Vue 组件)两种模式,但从代码来看,两者的模板差异主要在 data.ts 文件的有无。建议在文档中明确说明两种模式的适用场景和取舍,帮助开发者选择。


八、发散性思考

8.1 这个模块还能做什么?

方向一:AI 辅助代码生成

现在的 codegen 是"基于模板"的,字段名和控件类型的映射靠后缀匹配。如果引入 LLM(大语言模型),可以实现:

  • 根据表名 + 字段名 + 注释,自动推断业务语义(比如 amt 是金额、qty 是数量)
  • 根据业务语义自动推荐关联的字典类型
  • 甚至根据表结构自动生成部分业务逻辑代码(比如"库存不足时不允许下单")

方向二:反向工程

目前的流程是"DB → 代码"。如果支持"代码 → DB"——给一个 Java 实体类,自动生成建表 SQL 和 codegen 配置——就能形成双向闭环。其实 MyBatis-Plus Generator 本身就有这个能力,只是芋道目前没有集成。

方向三:API 文档自动生成

生成的 Controller 已经有 Swagger 注解了,但如果能进一步生成 Postman Collection 或 OpenAPI 3.0 规范文件,前端同学就可以直接用工具导入接口,不需要手动录入。

8.2 如果让我重新设计

如果从零开始设计一个代码生成器,我会考虑以下改进:

  1. 模板即代码:把 Velocity 模板改成 Java/TypeScript DSL(类似 JOOQ 那种风格),用代码写模板而不是用 .vm 文件。好处是 IDE 有语法提示、编译期检查、重构友好。
  2. 插件化架构:每种前端框架做成一个插件,按需加载。而不是现在把所有 101 个模板都打包在 JAR 里。
  3. 增量生成:目前每次生成都是全量的。如果支持"只生成变更的部分"(类似数据库 migration),对已有项目的侵入性更小。
  4. 可视化模板编辑器:允许开发者在管理后台在线编辑模板,实时预览效果,不需要重启服务。

8.3 技术迁移场景

codegen 模块的设计思路可以迁移到很多场景:

  • 报表生成器:把模板换成报表布局模板,把数据源换成 SQL 查询,就是一个报表代码生成器
  • API Mock 生成器:根据 Controller 的 Swagger 定义,自动生成 Mock 数据接口
  • 数据库文档生成器:根据 infra_codegen_table 和 infra_codegen_column 的数据,自动生成数据字典文档(其实已经有 sql.vm 模板了)
  • 多语言国际化文件生成器:根据数据库字段注释,自动生成 i18n 的 JSON/Properties 文件

九、关键代码导读

最后,列出 5 个最值得深入阅读的代码文件,按重要程度排序:

1. CodegenEngine.java(代码生成引擎)

路径: yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

为什么值得读: 这是整个 codegen 模块的核心,750+ 行代码。它定义了后端 16 个模板和前端 10 套模板的注册表,实现了 Velocity 模板的渲染流程,还有 prettyCode() 后处理逻辑。读完这个文件,你会理解"模板引擎"到底是怎么工作的。

关键看点:

  • SERVER_TEMPLATES 和 FRONT_TEMPLATES 的注册表设计
  • execute() 方法中根据 templateType 条件选择模板的逻辑
  • initGlobalBindingMap() 中注入的全局变量
  • Jakarta/Cloud 双模式的环境检测

2. CodegenBuilder.java(智能默认值构建器)

路径: yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java

为什么值得读: 这个文件展示了"如何用约定推断配置"的设计哲学。后缀匹配、类型推断、示例值生成——每一个方法都是对"企业 CRUD 共性"的提炼。如果你要设计自己的代码生成器,这个文件的思路可以直接复用。

关键看点:

  • COLUMN_LIST_OPERATION_CONDITION_MAPPINGS 查询条件映射表
  • COLUMN_HTML_TYPE_MAPPINGS UI 控件映射表
  • processColumnOperation() 中 BaseDO 字段的排除逻辑
  • processColumnExample() 的示例值生成策略

3. CodegenServiceImpl.java(业务编排层)

路径: yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java

为什么值得读: 这个文件是 codegen 的"指挥中心",编排了导入、更新、同步、生成四个核心流程。特别是 syncCodegen0() 方法中计算字段差异的逻辑(新增/修改/删除),是一个非常经典的"数据库 schema diff"实现。

关键看点:

  • syncCodegen0() 的三路差异计算(修改/删除/新增)
  • createCodegenList() 的事务管理和校验逻辑
  • generationCodes() 中主子表数据加载和校验

4. CodegenController.java(REST API 层)

路径: yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java

为什么值得读: 160 行代码,10 个接口,是学习"如何设计 RESTful API"的优秀范例。每个接口的 HTTP 方法选择、URL 命名、参数设计、权限控制都很规范。特别是 downloadCodegen() 方法中用 Hutool ZipUtil 打包 ZIP 的实现,简洁优雅。

关键看点:

  • ZIP 下载的 ZipUtil.zip() + writeAttachment() 实现
  • @PreAuthorize("@ss.hasPermission()") 权限控制模式
  • Swagger @Operation + @Parameter 注解的规范用法

5. CodegenTemplateTypeEnum.java(模板类型枚举)

路径: yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenTemplateTypeEnum.java

为什么值得读: 虽然只是一个枚举类,但它定义了 codegen 支持的三种模板模式(单表/树表/主子表),以及主子表的三个子模式。理解了这个枚举,就理解了 codegen 的能力边界。isMaster() 和 isTree() 这两个静态方法在 CodegenEngine 中被大量使用。


总结

RuoYi-Vue-Pro 的代码生成器,是我见过的开源项目中前端模板覆盖最广、主子表模式最丰富的代码生成器之一。它的设计哲学可以总结为三句话:

  1. 约定优于配置:通过字段名后缀推断查询条件、UI 控件、示例值,减少 80% 的手动配置
  2. 模板驱动生成:用 Velocity 模板 + Hutool 抽象层,实现了"换模板如换衣服"的灵活性
  3. 渐进式复杂度:单表 → 树表 → 主子表,从简单到复杂,按需选择

如果你正在做一个企业级后台管理系统,芋道的 codegen 模块绝对值得深入研究和借鉴。即使你不用 RuoYi,它的设计思路(后缀推断、模板注册表、schema diff 同步)也可以直接迁移到你自己的项目中。


系列文章导航:

下一篇预告: 基础设施篇的最后一个模块——文件存储/配置中心/定时任务/操作日志(yudao-module-infra 的其他子模块),敬请期待!

觉得有用的话,点个赞支持一下呗~ 有问题欢迎评论区讨论,我们下期见!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐