SpringCloud微服务实战——搭建企业级开发框架(三十一):自定义MybatisPlus代码生成器实现前后端代码自动生成

  理想的情况下,代码生成可以节省很多重复且没有技术含量的工作量,并且代码生成可以按照统一的代码规范和格式来生成代码,给日常的代码开发提供很大的帮助。但是,代码生成也有其局限性,当牵涉到复杂的业务逻辑时,简单的代码生成功能无法解决。
  目前市面上的代码生成器层出不穷,大多数的原理是基于已有的代码逻辑模板,按照一定的规则来生成CRUD代码。至于更为复杂的代码生成大家都在人工智能领域探索,目前基于代码训练的人工智能代码生成还在于提供代码补全功能方面,比如智能编程助手aiXcoder提供了常用IDE插件,在项目开发过程中,可以基于你项目的代码进行训练,编程时提供合适的代码提示。由微软、OpenAI、GitHub 三家联合打造的Copilot 也有异曲同工之妙,都是在项目开发中,提供优秀的代码自动补全功能从而可以提升工作效率。希望在不远的将来,我们可以实现复杂业务逻辑的代码也通过人工智能对大量代码的训练和分析来实现吧。

  这里我们制作的代码生成器,是按照平时开发过程中的思考来设计,一般情况下我们的开发步骤是: 需求分析->数据建模->数据库设计->编写后台代码(增删改查)->编写前台代码(增删改查)->字段校验 ->业务逻辑完善->测试,所以我们希望代码生成器能够:

  • 读取数据库表和字段
  • 根据数据库字段生成实体类和CRUD方法
  • 根据数据库字段生成前端操作页面
  • 前端页面的展示方式可以根据需要配置(form表单、数据展示列表)
  • 可以生成多表联合查询的代码
  • 可以配置字段的校验规则
一、引入依赖的库

1、修改GitEgg-Platform项目中的gitegg-platform-bom工程的pom.xml文件,这里使用mybatis-plus-generator目前最新版本3.5.1来自定义我们需要的代码生成器。

pom.xml

    <properties>
        ......
        <!-- Mybatis Plus增强工具代码生成 -->
        <mybatis.plus.generator.version>3.5.1</mybatis.plus.generator.version>
        ......
    </properties>

   <dependencyManagement>
        <dependencies>
           ......
            <!-- Mybatis Plus代码生成工具 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>${mybatis.plus.generator.version}</version>
            </dependency>
            ......
        </dependencies>
    </dependencyManagement>

2、在GitEgg-Platform项目中新建gitegg-platform-code-generator工程,提供基本的自定义代码生成能力,以及定义一些常量。

GitEggCodeGeneratorConstant.java常量类

package com.gitegg.platform.code.generator.constant;

import java.io.File;

/**
 * @ClassName: GitEggCodeGeneratorConstant 
 * @Description: 常量类
 * @author GitEgg
 * @since 2021-10-12
 */
public class GitEggCodeGeneratorConstant {

    /**
     * CONFIG
     */
    public static final String CONFIG = "config";

    /**
     * FIELDS
     */
    public static final String FIELDS = "fields";

    /**
     * FORM_FIELDS
     */
    public static final String FORM_FIELDS = "formFields";

    /**
     * BASE_ENTITY_FIELD_LIST
     */
    public static final String BASE_ENTITY_FIELD_LIST = "baseEntityFieldList";

    /**
     * Author
     */
    public static final String AUTHOR = "GitEgg";

    /**
     * JAVA_PATH
     */
    public static final String JAVA_PATH = File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;

    /**
     * RESOURCES_PATH
     */
    public static final String RESOURCES_PATH = File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator;

    /**
     * VUE_PATH
     */
    public static final String VUE_PATH = File.separator + "src" + File.separator + "views" + File.separator;

    /**
     * JS_PATH
     */
    public static final String JS_PATH = File.separator + "src" + File.separator + "api" + File.separator;

    /**
     * VUE_JS_PATH
     */
    public static final String VUE_JS_PATH = "vueJsPath";

    /**
     * CUSTOM_FILE_PATH_MAP
     */
    public static final String CUSTOM_FILE_PATH_MAP = "customFilePathMap";

}

3、mybatis-plus-generator3.5.1版本支持生成默认支持生成service、serviceImpl、mapper、mapperXml、controller、entity以及自定的other。这些文件都可以自定义模板和输出路径,但是mybatis-plus-generator是将所有的自定义文件都生成到other定义的目录下面的,这显然不符合我们的需求,比如我们需要的DTO文件,vue文件、js文件都会生成到不同的目录里面去,我们需要自定义扩展FreemarkerTemplateEngine方法,实现自定义文件生成到不同的目录,因为我们使用的是Freemarker所以自定义FreemarkerTemplateEngine这个实现类。

package com.gitegg.platform.code.generator.engine;

import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.io.File;
import java.util.Map;

/**
 * Freemarker 自定义输出自定义模板文件
 *
 * @author GitEgg
 * @since 2021-10-12
 */
public class GitEggFreemarkerTemplateEngine extends FreemarkerTemplateEngine {

    /**
     * 自定义输出自定义模板文件
     *
     * @param customFile 自定义配置模板文件信息
     * @param tableInfo  表信息
     * @param objectMap  渲染数据
     * @since 3.5.1
     */
    @Override
    protected void outputCustomFile( Map<String, String> customFile, TableInfo tableInfo, Map<String, Object> objectMap) {
        Map<String, String> customFilePath = (Map<String, String>)objectMap.get("customFilePathMap");
        customFile.forEach((key, value) -> {
            String otherPath = customFilePath.get(key);
            String fileName = String.format((otherPath + File.separator + "%s"), key);
            outputFile(new File(fileName), objectMap, value);
        });
    }
}

二、业务及实现方法

代码生成作为系统的一个功能模块,也需要考虑业务、数据库设计,这里主要有这几个模块:

  • 数据源配置:因为是微服务,可能会有多个数据库,分库分表等,所以这里选择使用配置数据源的方式,在代码生成的时候,让开发人员可以自己选择在哪个数据源下的表进行代码生成。
  • 代码生成基础配置(数据字典):代码生成时用到的组件类型、展示类型等基础配置,都配置的代码生成的数据字典中,这里不使用系统的数据字典。同时,在组件选择时,只可以选择业务的数据字典。
  • 校验规则配置:可以配置字段校验的正则表单式,在字段配置时选择哪些字段进行校验。
  • 代码生成规则配置:数据表配置、联合表配置、字段配置、表单配置、 校验配置、列表配置

1、根据以上业务需求,设计了t_sys_code_generator_datasource(数据源配置)、t_sys_code_generator_config(主数据表配置)、t_sys_code_generator_table_join(联表配置)、t_sys_code_generator_field(表字段配置)、t_sys_code_generator_validate(校验规则配置)、t_sys_code_generator_dict(数据字典配置)共六张表。

CREATE TABLE `t_sys_code_generator_datasource`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',
  `datasource_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据源名称',
  `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '连接地址',
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `driver` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据库驱动',
  `db_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据库类型',
  `comments` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据源配置表' ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_code_generator_config`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',
  `datasource_id` bigint(20) NULL DEFAULT NULL COMMENT '数据源',
  `module_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模块名称',
  `module_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模块代码',
  `service_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '服务名称',
  `table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表名',
  `table_alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表别名',
  `table_prefix` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表前缀',
  `parent_package` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级包名',
  `controller_path` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'controller路径',
  `form_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表单类型 modal弹出框  drawer抽屉  tab新窗口',
  `table_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表类型 single单表  multi多表',
  `table_show_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '展示类型 table数据表格 tree_table 树表格 3 left_tree_table左树右表  tree数据树  table_table左表右表  left_table_tree左表右树',
  `form_item_col` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表单字段排列 1一列一行  2 两列一行',
  `left_tree_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '左树类型 organization机构树 resource资源权限树 ',
  `front_code_path` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '前端代码路径',
  `service_code_path` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '后端代码路径',
  `import_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否支持导入 1支持 0不支持',
  `export_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否支持导出 1支持 0不支持',
  `query_reuse` tinyint(1) NOT NULL DEFAULT 1 COMMENT '查询复用:分页查询和单条记录查询公用同一个sql语句',
  `status_handling` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态处理',
  `code_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '代码生成类型  全部  仅后端代码  仅前端代码',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成配置表' ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_code_generator_table_join`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',
  `generation_id` bigint(20) NOT NULL COMMENT '代码生成主键',
  `datasource_id` bigint(20) NULL DEFAULT NULL COMMENT '数据源和主表一致',
  `join_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表名',
  `join_table_alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表别名',
  `join_table_prefix` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表前缀',
  `join_table_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'left左连接 right右连接 inner等值连接 union联合查询',
  `join_table_select` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自定义查询字段',
  `join_table_on` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自定义on条件',
  `table_sort` int(11) NULL DEFAULT NULL COMMENT '显示排序',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '多表查询时的联合表配置' ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_code_generator_field`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',
  `generation_id` bigint(20) NOT NULL COMMENT '代码生成主键',
  `join_id` bigint(20) NOT NULL COMMENT '关联表主键',
  `join_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表名',
  `field_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字段名称',
  `field_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字段类型',
  `comment` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字段描述',
  `entity_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体类型',
  `entity_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体名称',
  `form_add` tinyint(1) NOT NULL DEFAULT 0 COMMENT '表单新增',
  `form_edit` tinyint(1) NOT NULL DEFAULT 0 COMMENT '表单编辑',
  `query_term` tinyint(1) NOT NULL DEFAULT 0 COMMENT '查询条件',
  `list_show` tinyint(1) NOT NULL DEFAULT 0 COMMENT '列表展示',
  `import_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否支持导入 1支持 0不支持',
  `export_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否支持导出 1支持 0不支持',
  `required` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否必填',
  `field_unique` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否唯一',
  `query_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '查询类型',
  `control_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件类型',
  `dict_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典编码',
  `min` bigint(20) NULL DEFAULT NULL COMMENT '最小值',
  `max` bigint(20) NULL DEFAULT NULL COMMENT '最大值',
  `min_length` int(11) NOT NULL DEFAULT 0 COMMENT '最小长度',
  `max_length` int(11) NULL DEFAULT NULL COMMENT '字段最大长度',
  `default_value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '默认值',
  `validate_id` bigint(20) NULL DEFAULT NULL COMMENT '校验规则主键',
  `validate_regular` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自定义正则表达式校验规则',
  `field_sort` int(11) NOT NULL DEFAULT 1 COMMENT '显示排序',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `unique_field`(`generation_id`, `join_id`, `join_table_name`, `field_name`) USING BTREE COMMENT '联合约束'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字段属性配置表' ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_code_generator_validate`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',
  `validate_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '校验名称',
  `validate_regular` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '正则表达式校验规则',
  `status` tinyint(2) NOT NULL DEFAULT 1 COMMENT '\'0\'禁用,\'1\' 启用',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者',
  `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字段校验规则配置表' ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_code_generator_dict`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id',
  `parent_id` bigint(20) NULL DEFAULT NULL COMMENT '字典上级',
  `ancestors` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所有上级字典id的集合,便于查找',
  `dict_name` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '字典名称',
  `dict_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '字典值',
  `dict_order` int(11) NULL DEFAULT NULL COMMENT '排序',
  `dict_status` tinyint(2) NULL DEFAULT 1 COMMENT '1有效,0禁用',
  `comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `operator` bigint(20) NULL DEFAULT NULL COMMENT '操作人',
  `del_flag` tinyint(2) NOT NULL DEFAULT 0 COMMENT '1:删除 0:不删除',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `INDEX_DICT_NAME`(`dict_name`) USING BTREE,
  INDEX `INDEX_DICT_CODE`(`dict_code`) USING BTREE,
  INDEX `INDEX_PARENT_ID`(`parent_id`) USING BTREE,
  INDEX `INDEX_TENANT_ID`(`tenant_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '数据字典表' ROW_FORMAT = Dynamic;

  表结构建立好之后,先用mybatis-plus-generator默认功能生成基本的CRUD代码,这些CRUD代码就不列出来了,主要说明如何利用mybatis-plus-generator读取数据库表和字段,并结合业务在界面上展示,从而进行代码生成规则的配置。
2、在GitEgg-Cloud项目下,gitegg-plugin子项目下新建gitegg-code-generator工程,新建IEngineService接口和接口实现类EngineServiceImpl用于实现:查询某个数据源的所有表、查询某个表的字段信息、查询某个代码生成配置里面所有的字段配置、执行代码生成功能。

package com.gitegg.code.generator.engine.service;

import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.gitegg.code.generator.config.dto.QueryConfigDTO;
import com.gitegg.code.generator.engine.dto.TableDTO;

import java.util.List;

/**
 * 代码生成器接口
 *
 * @author GitEgg
 */
public interface IEngineService {

    /**
     * 查询某个数据源的所有表
     *
     * @param queryConfigDTO
     * @return
     */
    List<TableDTO> queryTableList(QueryConfigDTO queryConfigDTO);

    /**
     * 查询某个数据源表的字段信息
     *
     * @param datasourceId
     * @param tableNames
     * @return
     */
    List<TableInfo> queryTableFields(String datasourceId, List<String> tableNames);

    /**
     * 查询某个代码生成配置里面所有的字段
     * @param queryConfigDTO
     * @return
     */
    List<TableInfo> queryConfigFields(QueryConfigDTO queryConfigDTO);

    /**
     * 执行代码生成
     * @param queryConfigDTO
     * @return
     */
    boolean processGenerateCode(QueryConfigDTO queryConfigDTO);
}

package com.gitegg.code.generator.engine.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.gitegg.code.generator.config.dto.QueryConfigDTO;
import com.gitegg.code.generator.config.entity.Config;
import com.gitegg.code.generator.config.service.IConfigService;
import com.gitegg.code.generator.datasource.entity.Datasource;
import com.gitegg.code.generator.datasource.service.IDatasourceService;
import com.gitegg.code.generator.engine.GitEggDatabaseQuery;
import com.gitegg.code.generator.engine.constant.CodeGeneratorConstant;
import com.gitegg.code.generator.engine.dto.TableDTO;
import com.gitegg.code.generator.engine.enums.CustomFileEnum;
import com.gitegg.code.generator.engine.service.IEngineService;
import com.gitegg.code.generator.field.dto.FieldDTO;
import com.gitegg.code.generator.field.dto.QueryFieldDTO;
import com.gitegg.code.generator.field.service.IFieldService;
import com.gitegg.code.generator.join.entity.TableJoin;
import com.gitegg.code.generator.join.service.ITableJoinService;
import com.gitegg.platform.base.enums.BaseEntityEnum;
import com.gitegg.platform.code.generator.constant.GitEggCodeGeneratorConstant;
import com.gitegg.platform.code.generator.engine.GitEggFreemarkerTemplateEngine;
import com.gitegg.platform.mybatis.entity.BaseEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 代码生成器接口类
 *
 * @author GitEgg
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class EngineServiceImpl implements IEngineService {

    private final IConfigService configService;

    private final IDatasourceService datasourceService;

    private final ITableJoinService tableJoinService;

    /**
     * 解决循环依赖问题
     */
    private IFieldService fieldService;

    @Autowired
    public void setFieldService(@Lazy IFieldService fieldService) {
        this.fieldService = fieldService;
    }

    @Override
    public List<TableDTO> queryTableList(QueryConfigDTO queryConfigDTO) {
        Datasource datasource = datasourceService.getById(queryConfigDTO.getDatasourceId());
        DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(datasource.getUrl(), datasource.getUsername(), datasource.getPassword()).build();
        ConfigBuilder configBuilder = new ConfigBuilder(null, dataSourceConfig, null, null, null, null);
        List<TableDTO> tableInfos = (new GitEggDatabaseQuery(configBuilder)).queryDatasourceTables();
        return tableInfos;
    }

    @Override
    public List<TableInfo> queryTableFields(String datasourceId, List<String> tableNames) {
        Datasource datasource = datasourceService.getById(datasourceId);
        DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(datasource.getUrl(), datasource.getUsername(), datasource.getPassword()).build();

        //设置有哪些表
        StrategyConfig strategyConfig = new StrategyConfig.Builder()
                .addInclude(tableNames.toArray(new String[]{}))
                .entityBuilder()
                .enableChainModel()
                .enableLombok()
                .enableRemoveIsPrefix()
                .enableTableFieldAnnotation()
                .enableActiveRecord()
                .logicDeleteColumnName(BaseEntityEnum.DEL_FLAG.field)
                .logicDeletePropertyName(BaseEntityEnum.DEL_FLAG.entity)
                .naming(NamingStrategy.underline_to_camel)
                .columnNaming(NamingStrategy.underline_to_camel)
                .addTableFills(new Column(BaseEntityEnum.CREATE_TIME.field, FieldFill.INSERT))
                .addTableFills(new Column(BaseEntityEnum.UPDATE_TIME.field, FieldFill.INSERT_UPDATE))
                .idType(IdType.AUTO)
                .build();

        ConfigBuilder configBuilder = new ConfigBuilder(null, dataSourceConfig, strategyConfig, null, null, null);
        List<TableInfo> tableInfoList = configBuilder.getTableInfoList();
        return tableInfoList;
    }

    @Override
    public List<TableInfo> queryConfigFields(QueryConfigDTO queryConfigDTO) {
        List<String> tableNames = new ArrayList<>();
        String tableName = queryConfigDTO.getTableName();
        tableNames.add(tableName);

        Long id = queryConfigDTO.getId();

        // 查询是否有联表
        if (CodeGeneratorConstant.TABLE_DATA_TYPE_MULTI.equals(queryConfigDTO.getTableType()))
        {
            QueryWrapper<TableJoin> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq(CodeGeneratorConstant.GENERATION_ID, id);
            List<TableJoin> tableJoinList = tableJoinService.list(queryWrapper);
            if(!CollectionUtils.isEmpty(tableJoinList))
            {
                tableJoinList.stream().forEach(tableJoin->{
                    tableNames.add(tableJoin.getJoinTableName());
                });
            }
        }

        Datasource datasource = datasourceService.getById(queryConfigDTO.getDatasourceId());
        DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(datasource.getUrl(), datasource.getUsername(), datasource.getPassword()).build();

        //设置有哪些表
        StrategyConfig strategyConfig = new StrategyConfig.Builder().addInclude(tableNames.toArray(new String[]{})).build();
        ConfigBuilder configBuilder = new ConfigBuilder(null, dataSourceConfig, strategyConfig, null, null, null);
        List<TableInfo> tableInfoList = configBuilder.getTableInfoList();
        return tableInfoList;
    }

    @Override
    public boolean processGenerateCode(QueryConfigDTO queryConfigDTO){

        Config config = configService.getById(queryConfigDTO.getId());

        QueryFieldDTO queryFieldDTO = new QueryFieldDTO();
        queryFieldDTO.setGenerationId(queryConfigDTO.getId());
        List<FieldDTO> fieldDTOS = fieldService.queryFieldList(queryFieldDTO);

        //提取表单的字段
        List<FieldDTO> formFieldDTOS = fieldDTOS.stream().filter(f->f.getFormAdd() || f.getFormEdit()).collect(Collectors.toList());

        Map<String, Object> customMap = new HashMap<>();
        customMap.put(GitEggCodeGeneratorConstant.CONFIG, config);
        customMap.put(GitEggCodeGeneratorConstant.FIELDS, fieldDTOS);
        customMap.put(GitEggCodeGeneratorConstant.FORM_FIELDS, formFieldDTOS);

        //baseEntity里面有的,DTO中需要排除的字段
        List<String> baseEntityFieldList = BaseEntityEnum.getBaseEntityFieldList();
        customMap.put(GitEggCodeGeneratorConstant.BASE_ENTITY_FIELD_LIST, baseEntityFieldList);

        //查询数据源配置
        Datasource datasource = datasourceService.getById(config.getDatasourceId());

        String serviceName = config.getServiceName();
        //前端代码路径
        String frontCodePath = config.getFrontCodePath();
        //后端代码路径
        String serviceCodePath = config.getServiceCodePath();
        //自定义路径
        String parent = config.getParentPackage();
        String moduleName = config.getModuleCode();
        String codeDirPath =  (parent + StrUtil.DOT + moduleName).replace(StrUtil.DOT, File.separator) + File.separator;

        FastAutoGenerator.create(datasource.getUrl(), datasource.getUsername(), datasource.getPassword())
                .globalConfig(builder -> {
                    //全局配置
                    String author = GitEggCodeGeneratorConstant.AUTHOR;
                    builder.author(author) // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .disableOpenDir()
                            .outputDir(serviceCodePath + GitEggCodeGeneratorConstant.JAVA_PATH); // 指定输出目录
                })
                .packageConfig(builder -> {
                    //包配置
                    Map<OutputFile, String> pathInfoMap = new HashMap<>();
                    pathInfoMap.put(OutputFile.mapperXml, serviceCodePath + GitEggCodeGeneratorConstant.RESOURCES_PATH + codeDirPath + CodeGeneratorConstant.MAPPER);
                    builder.parent(parent) // 设置父包名
                            .moduleName(moduleName) // 设置父包模块名
                            .pathInfo(pathInfoMap); // 自定义生成路径
                })
                .injectionConfig(builder -> {

                    String dtoName = StrUtil.upperFirst(config.getModuleCode());

                    //dto
                    String dtoFile = dtoName + CodeGeneratorConstant.DTO_JAVA;
                    String createDtoFile = CodeGeneratorConstant.CREATE + dtoFile;
                    String updateDtoFile = CodeGeneratorConstant.UPDATE + dtoFile;
                    String queryDtoFile = CodeGeneratorConstant.QUERY + dtoFile;
                    //Export and Import
                    String exportFile = dtoName + CodeGeneratorConstant.EXPORT_JAVA;
                    String importFile = dtoName + CodeGeneratorConstant.IMPORT_JAVA;
                    // SQL
                    String sqlFile = dtoName + CodeGeneratorConstant.RESOURCE_SQL;

                    // 设置自定义输出文件
                    Map<String, String> customFileMap = new HashMap<>();
                    customFileMap.put(dtoFile, CustomFileEnum.DTO_FILE.path);
                    customFileMap.put(createDtoFile, CustomFileEnum.CREATE_DTO.path);
                    customFileMap.put(updateDtoFile, CustomFileEnum.UPDATE_DTO.path);
                    customFileMap.put(queryDtoFile, CustomFileEnum.QUERY_DTO.path);
                    // Export and Import
                    customFileMap.put(exportFile, CustomFileEnum.EXPORT.path);
                    customFileMap.put(importFile, CustomFileEnum.IMPORT.path);
                    // SQL
                    customFileMap.put(sqlFile, CustomFileEnum.SQL.path);

                    //因为目前版本框架只支持自定义输出到other目录,所以这里利用重写AbstractTemplateEngine的outputCustomFile方法支持所有自定义文件输出目录
                    Map<String, String> customFilePath = new HashMap<>();

                    int start = serviceName.indexOf(StrUtil.DASHED);
                    int end = serviceName.length();
                    String servicePath = serviceName.substring(start, end).replace(StrUtil.DASHED, File.separator);

                    //判断是否生成后端代码
                    if (config.getCodeType().equals(CodeGeneratorConstant.CODE_ALL) || config.getCodeType().equals(CodeGeneratorConstant.CODE_SERVICE))
                    {
                        //dto
                        String dtoPath = serviceCodePath + GitEggCodeGeneratorConstant.JAVA_PATH + codeDirPath + CodeGeneratorConstant.DTO;
                        customFilePath.put(dtoFile, dtoPath);
                        customFilePath.put(createDtoFile, dtoPath);
                        customFilePath.put(updateDtoFile, dtoPath);
                        customFilePath.put(queryDtoFile, dtoPath);
                        // Export and Import
                        String entityPath = serviceCodePath + GitEggCodeGeneratorConstant.JAVA_PATH + codeDirPath + CodeGeneratorConstant.ENTITY;
                        customFilePath.put(exportFile, entityPath);
                        customFilePath.put(importFile, entityPath);
                        // SQL
                        String sqlPath = serviceCodePath + GitEggCodeGeneratorConstant.RESOURCES_PATH + codeDirPath + CodeGeneratorConstant.MAPPER;
                        customFilePath.put(sqlFile, sqlPath);


                    }

                    //判断是否生成后端代码
                    if (config.getCodeType().equals(CodeGeneratorConstant.CODE_ALL) || config.getCodeType().equals(CodeGeneratorConstant.CODE_FRONT))
                    {
                        // vue and js
                        String vueFile = config.getModuleCode() + CodeGeneratorConstant.TABLE_VUE;
                        String jsFile = config.getModuleCode() + CodeGeneratorConstant.JS;

                        String vuePath = frontCodePath + GitEggCodeGeneratorConstant.VUE_PATH + servicePath + File.separator + config.getModuleCode();
                        String jsPath = frontCodePath + GitEggCodeGeneratorConstant.JS_PATH + servicePath + File.separator + config.getModuleCode();
                        customFilePath.put(vueFile, vuePath);
                        customFilePath.put(jsFile, jsPath);
                        // VUE AND JS
                        // TODO 要支持树形表、左树右表、左表右表、左表右树、左树右树形表、左树右树
                        customFileMap.put(vueFile, CustomFileEnum.VUE.path);
                        customFileMap.put(jsFile, CustomFileEnum.JS.path);
                        customMap.put(GitEggCodeGeneratorConstant.VUE_JS_PATH, servicePath.replace(File.separator, StrUtil.SLASH) + StrUtil.SLASH + config.getModuleCode() + StrUtil.SLASH + config.getModuleCode());
                    }

                    customMap.put(GitEggCodeGeneratorConstant.CUSTOM_FILE_PATH_MAP, customFilePath);

                    builder.customMap(customMap)
                            .customFile(customFileMap);
                })
                .strategyConfig(builder -> {
                    builder
                            .addInclude(config.getTableName())
                            .addTablePrefix(config.getTablePrefix())
                            .entityBuilder()
                            .enableLombok()
                            .enableTableFieldAnnotation() // 实体字段注解
                            .superClass(BaseEntity.class)
                            .addSuperEntityColumns(BaseEntityEnum.TENANT_ID.field, BaseEntityEnum.CREATE_TIME.field,
                                    BaseEntityEnum.CREATOR.field, BaseEntityEnum.UPDATE_TIME.field, BaseEntityEnum.OPERATOR.field, BaseEntityEnum.DEL_FLAG.field)
                            .naming(NamingStrategy.underline_to_camel)
                            .addTableFills(new Column(BaseEntityEnum.CREATE_TIME.field, FieldFill.INSERT))  //基于数据库字段填充
                            .addTableFills(new Column(BaseEntityEnum.UPDATE_TIME.field, FieldFill.INSERT_UPDATE))   //基于模型属性填充
                            .controllerBuilder()
                            .enableRestStyle()
                            .enableHyphenStyle()
                            .mapperBuilder()
//                            .enableMapperAnnotation()
                            .enableBaseResultMap()
                            .enableBaseColumnList()
                    ;
                })
                .templateConfig(builder -> {
                    if (config.getCodeType().equals(CodeGeneratorConstant.CODE_FRONT)) {
                        builder.disable();
                    }
                })
                // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .templateEngine(new GitEggFreemarkerTemplateEngine())
                .execute();
        return true;
    }
}

3、修改代码生成的模板文件,因为默认的代码模板生成文件不能满足我们的需求,我们需要新增DTO、vue、js、数据导入导出实体定义类等模板,在模板接口新增导入导出等方法,在DTO添加字段校验等。因为模板代码太多,这里不详细列举,可以在在GitHub 或者 Gitee下载查看。
4、代码生成功能运行界面

数据源配置:


数据源配置

代码生成配置:


代码生成配置

关联表配置:
关联表配置

表字段配置:


表字段配置

表单配置:
表单配置

表单校验配置:
表单校验配置

列表查询配置:
列表查询配置

数据字典配置:
数据字典配置

校验规则配置:


校验规则配置
GitEgg-Cloud是一款基于SpringCloud整合搭建的企业级微服务应用开发框架,开源项目地址:

Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg

欢迎感兴趣的小伙伴Star支持一下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容