跳至主要內容

若依源码解析:代码生成ruoyi-generator

程序员诚哥大约 12 分钟若依若依后端代码自动生成源码解析

摘要

若依的代码生成器模块(ruoyi-generator)可以根据数据库表的设计信息和配置的模板,自动生成相应的Java代码文件。代码生成器使用Velocity作为模板引擎,根据模板文件中的占位符和变量替换规则,将元数据信息嵌入到生成的代码中,生成具体的代码文件。通过导入表结构和生成代码两个后端接口,实现了快速导入数据库表结构和生成代码的功能。导入表结构会从information_schema数据库的tables和columns表中查询表和列的信息,并插入到ruoyi数据库的gen_table和gen_table_column表中。生成代码时,会根据查询到的表和列信息,初始化Velocity模板引擎,并准备上下文信息,包括变量值信息。然后,读取模板文件,渲染模板,并将渲染后的内容添加到压缩流中生成zip压缩文件,供前端下载使用。ruoyi-vue代码生成器大大提高了开发效率,使得开发人员能够快速生成符合规范的代码文件。

代码生成器的使用

通过以下步骤,若依可以根据数据库表的设计信息自动生成相应的代码文件,极大地提高了开发效率。开发人员可以根据生成的代码文件进行进一步的开发和定制。

以下是若依实现代码自动生成的一般流程:

数据库连接配置

首先,若依需要配置数据库连接信息,包括数据库类型、地址、用户名和密码等。

数据库表设计

在数据库中设计和定义表结构,包括表名、字段名、数据类型、约束等。
若依建表有个要求:表字段 和 表,都需要加注释,注释就是生成页面的显示内容

CREATE TABLE `goods` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `GOODS_NAME` varchar(255) DEFAULT NULL COMMENT '商品名字',
  `put_way_flag` tinyint(1) DEFAULT NULL COMMENT '商品是否上架,0:下架,1:上架',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `update_by` varchar(64) DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'

代码生成器配置

若依提供了一个代码生成器,通过application.yml配置文件可以设置生成器的参数,如生成路径、包名、作者等。

# 代码生成
gen: 
  # 作者
  author: zcc
  # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
  packageName: com.cyl.pms
  # 自动去除表前缀,默认是false
  autoRemovePre: false
  # 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
  tablePrefix: pms_

另外,这里要使用自定义包名com.cyl.pms,所以若依系统中mybatis也要做相应的修改

修改mybatis别名配置,增加对com.cyl包名的识别

# MyBatis配置
mybatis:
    # 搜索指定包别名
    typeAliasesPackage: com.ruoyi.**.domain,com.cyl.**.domain

修改mybatis的mapper扫描包路径

修改com.ruoyi.framework.config.ApplicationConfig类的MapperScan注解,增加对com.cyl包的扫描

@MapperScan({"com.ruoyi.**.mapper","com.cyl.**.mapper"})
public class ApplicationConfig{
    ...
}

代码生成

若依根据配置和模板,通过解析数据库表的元数据信息,自动生成对应的Java类、Mapper接口、Service类、Controller类等代码文件。
进入系统工具-代码生成页面,点击导入按钮,找到goods表并导入,如下图所示

点击编辑按钮之后,跳转修改生成配置页面。

点击生成代码按钮

代码输出

生成的代码文件可以输出到指定的目录中,可以选择直接写入磁盘文件或者打包成压缩文件。
生成的代码目录结构如下所示

├── goodsMenu.sql
├── main
│   ├── java
│   │   └── com
│   │       └── kdyzm
│   │           └── business
│   │               ├── controller
│   │               │   └── GoodsController.java
│   │               ├── domain
│   │               │   └── Goods.java
│   │               ├── mapper
│   │               │   └── GoodsMapper.java
│   │               └── service
│   │                   ├── IGoodsService.java
│   │                   └── impl
│   │                       └── GoodsServiceImpl.java
│   └── resources
│       └── mapper
│           └── business
│               └── GoodsMapper.xml
└── vue
    ├── api
    │   └── business
    │       └── goods.js
    └── views
        └── business
            └── goods
                └── index.vue

模板配置

若依代码生成器支持三种数据格式模板的代码生成:单表、树表、主子表,一般默认使用的是单表模板,也是最常使用的模板。

代码生成器原理

模板引擎:Velocity

在若依(Ruoyi)项目的代码生成中,使用了Apache Velocity作为模板引擎来生成具体的代码。

使用Velocity模板引擎的一般流程

Velocity是一个Java模板引擎,它使用简单的模板语法和变量替换规则来生成文本输出。以下是若依项目中使用Velocity模板引擎的一般流程:

  1. 引入Velocity依赖:
    首先,在项目的构建文件中(如pom.xml)添加Velocity的依赖项,以便在代码中使用Velocity的相关类和方法。

  2. 获取模板:
    在代码生成过程中,若依会根据配置的模板文件路径,使用Velocity模板引擎获取指定的模板。通常,模板文件以.vm为后缀,如xxx.java.vm表示Java类的模板。

  3. 创建Velocity模板引擎对象:
    若依通过Velocity.getTemplate(template, charset)方法创建Velocity模板引擎对象。template参数是模板文件的路径,charset参数是模板文件的字符编码。

  4. 准备上下文数据:
    在代码生成过程中,若依会准备一些上下文数据,以便在模板中使用。这些数据通常包括表信息、字段信息和其他生成参数等。上下文数据被封装在一个context对象中,可以通过context.put(key, value)方法添加到上下文中。

  5. 渲染模板:
    使用Template.merge(context, writer)方法,将上下文数据应用于模板,并将渲染后的结果写入指定的输出writer中。在若依中,通常使用StringWriter作为输出的writer,以便将渲染后的代码保存为字符串。

  6. 获取渲染结果:
    通过StringWriter.toString()方法获取渲染后的代码字符串,即生成的具体代码。

  7. 输出代码:
    生成的代码可以输出到指定的路径或文件中。若依中使用FileUtils.writeStringToFile(file, content, charset)方法将代码字符串写入文件中。

模板语法

Velocity模板引擎使用简洁而强大的模板语法。在若依项目中,模板文件通常采用.java.vm的后缀,表示Java类的模板。在模板文件中,可以使用Velocity的标签、变量和指令来定义动态内容和控制流程。

一些常用的Velocity模板语法包括:

  • 变量替换:使用${variable}来引用变量。
  • 条件语句:使用#if#elseif#else来控制条件判断。
  • 循环语句:使用#foreach来进行循环迭代。
  • 宏定义:使用#macro来定义可重用的宏。
  • 注释:使用##进行单行注释。

上下文数据

在代码生成过程中,若依会准备上下文数据,用于传递给Velocity模板引擎。上下文数据通常包括表信息、字段信息和其他生成参数等。这些数据被封装在一个context对象中,可以使用context.put(key, value)方法将数据添加到上下文中。

在模板中,可以通过${key}的形式引用上下文中的数据。例如,${table.name}表示引用上下文中表名的值。

information_schema

MySQL数据库中的information_schema数据库是MySQL的系统数据库之一,它包含了关于数据库、表、列等元数据信息的表。在代码生成器中,正是利用了information_schema数据库中的TABLES表和COLUMNS表来获取数据库表的信息,从而实现代码的生成。

  1. TABLES表:
    TABLES表存储了数据库中所有表的信息,包括表名、表所属的数据库、表的引擎类型、创建时间、更新时间等。通过查询TABLES表,代码生成器可以获取到需要生成代码的表的相关信息,如表名、表所在的数据库等。

  2. COLUMNS表:
    COLUMNS表存储了数据库中所有表的列信息,包括列名、数据类型、列的默认值、是否允许为空、字符集等。通过查询COLUMNS表,代码生成器可以获取到需要生成代码的表的列信息,如列名、数据类型等。

通过查询TABLES表和COLUMNS表,代码生成器可以获得数据库表的设计信息,包括表名、列名、数据类型等。这些信息可以用于生成代码文件,例如生成实体类的属性、Mapper接口的方法等。通过结合模板引擎,将这些数据库表的设计信息嵌入到模板中,就可以自动生成具体的Java代码文件。

代码生成器利用information_schema数据库中的TABLES表和COLUMNS表,提供了一种便捷的方式来获取数据库表的结构信息,使得生成的代码与数据库表的设计保持一致,减少了手动编写代码的工作量,并提高了代码的一致性和可维护性。

源码解析

在ruoyi-vue项目的ruoyi-generator模块中,代码生成器相关的代码实现了以下步骤。
通过以下步骤,ruoyi-vue代码生成器可以根据数据库表的结构信息和模板文件,生成具体的代码文件。导入表结构步骤将表和列信息存储到数据库中,生成代码步骤则利用这些信息和模板进行代码的生成和打包。这样可以实现代码的快速生成和下载,提高开发效率。

导入表结构(com.ruoyi.generator.controller.GenController#importTableSave接口)

@PreAuthorize("@ss.hasPermi('tool:gen:import')")
    @Log(title = "代码生成", businessType = BusinessType.IMPORT)
    @PostMapping("/importTable")
    public AjaxResult importTableSave(String tables)
    {
        String[] tableNames = Convert.toStrArray(tables);
        // 查询表信息
        List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
        genTableService.importGenTable(tableList, SecurityUtils.getUserId());
        return AjaxResult.success();
    }
  • 通过查询information_schema数据库的tables表,获取目标表的表名、表注释、创建时间和更新时间。这里忽略了定时任务的表和已经生成过的表。
  • 初始化表数据,并将数据插入ruoyi数据库的gen_table表中。
  • 通过查询information_schema数据库的columns表,获取目标表的列信息,包括字段名、字段注释、字段类型、是否允许为null等详细信息。
  • 初始化列信息,并将数据插入ruoyi数据库的gen_table_column表中。

生成代码(com.ruoyi.generator.controller.GenController#batchGenCode接口)

@PreAuthorize("@ss.hasPermi('tool:gen:code')")
    @Log(title = "代码生成", businessType = BusinessType.GENCODE)
    @GetMapping("/batchGenCode")
    public void batchGenCode(HttpServletResponse response, String tables) throws IOException
    {
        String[] tableNames = Convert.toStrArray(tables);
        byte[] data = genTableService.downloadCode(tableNames);
        genCode(response, data);
    }
  • 从ruoyi数据库的gen_table表和gen_table_column表中查询出生成代码所需的表和列信息。
  • 初始化Velocity模板引擎。
  • 准备Velocity上下文信息,包括变量值信息。上下文中存储了从数据库查询到的表和列信息,以及其他生成参数。
  • 读取模板文件,渲染模板,并将渲染后的模板内容添加到压缩流中,最后生成zip压缩文件供前端下载。

generatorCoded方法

/**
     * 生成代码(自定义路径)
     *
     * @param tableName 表名称
     */
    @Override
    public void generatorCode(String tableName) {
        // 查询表信息
        GenTable table = genTableMapper.selectGenTableByName(tableName);
        Result result = getResult(table);
        for (String template : result.templates) {
            if (template.endsWith(".java.vm")) {
                result.context.put("fullPackage", getFullPackage(template));
            }
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
            tpl.merge(result.context, sw);
            String path = null;
            try {
                path = generatePath(template, table);
                File file = new File(path);
                FileUtils.writeStringToFile(file, sw.toString(), CharsetKit.UTF_8);
                log.info("{}", file.getAbsoluteFile());
            } catch (IOException e) {
                throw new ServiceException("渲染模板失败,表名:" + table.getTableName() + ", path: " + path);
            }
        }
    }

这段代码是在若依(Ruoyi)项目的代码生成器中的一个方法,用于生成代码并写入文件。

具体解释如下:

  1. 方法签名:
    public void generatorCode(String tableName)

    该方法接受一个参数 tableName,表示要生成代码的表名。

  2. 查询表信息:
    GenTable table = genTableMapper.selectGenTableByName(tableName);

    通过 genTableMapper(数据访问层)根据表名查询数据库中的表信息,并将结果保存在 GenTable 对象 table 中。

  3. 获取模板结果:
    Result result = getResult(table);

    通过调用 getResult 方法,根据查询到的表信息 table,获取生成代码所需的模板文件和上下文数据,结果保存在 Result 对象 result 中。

  4. 遍历模板文件:
    for (String template : result.templates)

    遍历 result 中的模板文件列表。

  5. 处理 Java 模板:

    if (template.endsWith(".java.vm")) {
        result.context.put("fullPackage", getFullPackage(template));
    }
    

    如果模板文件以 .java.vm 结尾,表示为 Java 类的模板文件。将生成的代码所属的包名(通过 getFullPackage 方法获取)放入上下文数据 result.context 中。

  6. 渲染模板:

    StringWriter sw = new StringWriter();
    Template tpl = Velocity.getTemplate(template, Constants.UTF8);
    tpl.merge(result.context, sw);
    

    使用 Velocity 模板引擎,根据模板文件和上下文数据,将模板渲染为具体的代码,结果保存在 StringWriter 对象 sw 中。

  7. 生成文件:

    String path = generatePath(template, table);
    File file = new File(path);
    FileUtils.writeStringToFile(file, sw.toString(), CharsetKit.UTF_8);
    

    根据模板文件和表信息,生成代码文件的路径。然后创建文件对象 file,将渲染后的代码内容写入文件中。

  8. 日志输出:
    log.info("{}", file.getAbsoluteFile());

    输出生成的代码文件的绝对路径。

  9. 异常处理:

    } catch (IOException e) {
        throw new ServiceException("渲染模板失败,表名:" + table.getTableName() + ", path: " + path);
    }
    

    如果在渲染模板和生成文件的过程中发生了异常,抛出自定义的 ServiceException 异常,并提供失败的表名和路径信息。

通过以上步骤,这段代码会根据表名查询表信息,获取模板文件和上下文数据,然后遍历模板文件,将模板渲染为具体的代码,最后生成代码文件并写入磁盘。

上次编辑于:
贡献者: zccbbg