手把手带你实现一个DDD与MVC架构代码生成器

前言

不知各位看官在工作之中有没有陷入过疯狂CV代码、看着密密麻麻的类不想动手,或者把大把的时间花费在底层的情况。以笔者为例,会经常遇到以下两个问题:

  1. 新需求一堆的Entity、Bean、Request、Response、DTO、Dao、Service、Business需要写,看着都不想动手
  2. 个人项目创建完表后,总是要手动创建所有代码结构。不管是使用 MP 还是使用 EasyCode 生成的 代码都不太符合我的要求

很多时候甚至会在复制粘贴代码时漏掉一些关键的注解,比如:@Service,导致项目无法启动,再花费更多的精力去排查。并且由于新公司的 DDD 领域驱动架构,所以生成代码更加的复杂。往往初始化一个模块要大半个小时!于是,就有了这个产品的诞生。支持 DDD 架构和 MVC 架构的代码生成器~


开发步骤

笔者开发这个代码生成器还有个很重要的目的:就是希望能够尽量少的更改配置,来做到满足各类需求

构建配置文件

由于本项目涉及数据库层面操作,所以除了个性化的作者、需要生成的表名等信息以外,还需要数据库相关配置等。配置文件如下:

# mysql 相关配置
database:
    driver: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/cloud_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 


# 需要生成的表名,默认为 * 代表所有表,多个表名以 , 分割
# TODO 在 YML 中, * 是一个锚点引用符,所以需要用引号包裹表明此处是个普通字符串
tableNames: "*"

# 表名前缀, 默认为 "*" 不去除前缀
tablePrefix: "*"

# 作者
author: zhanggongming

# 自定义前缀   eg: 我想生成的文件在 com.zhang.system,此处就填写 system。默认为 *,代表直接生成在 packageName 目录下
prefix: "*"

# 生成的代码 package 包名
packageName: com.zhang

# 是否只生成资源层  (DDD 架构专用属性)
onlyResource: false

考虑好基础的配置内容后,通过读取配置文件,就可以将相关信息置入配置中心

读取配置文件信息

配置文件定义出来后,我们就可以将配置加载进系统中,核心代码如下:

/**
 * 加载配置文件
 */
public static void loadProperties() throws IOException {
    // 1.加载 yml 文件
    String filePath = System.getProperty("user.dir") + File.separator + "target/classes/application.yml";

    InputStream inputStream;
    if (new File(filePath).exists()) {
        inputStream = Files.newInputStream(Paths.get(filePath));
    } else {
        inputStream = PropertiesFactory.class.getClassLoader().getResourceAsStream("application.yml");
    }

    // 2.解析 yml 文件
    Yaml yml = new Yaml();
    ConfigInfo configInfo = yml.loadAs(inputStream, ConfigInfo.class);
    // 解析项目目录地址
    String projectPath = System.getProperty("user.dir");
    configInfo.setProjectPath(projectPath);
    GlobalConfig.setConfigInfo(configInfo);
    log.info("YML load Successful, configInfo is [{}]", configInfo);
}

在实现中,用到了双重非空检测结合单例模式确保只会加载一次配置文件。具体代码可以文末链接自取~

本阶段涉及的类:DefaultStrategyImpl (DDD 架构策略)、MVCStrategyImpl(MVC 架构策略 - 看任意一个就行)、GlobalConfig、PropertiesFactory

入口方法:com.zhang.generate.factory.GenerateStrategyFactory#registerStrategy

获取数据库表、字段信息

在获取到目标数据库等的配置信息后,接下来就是要通过数据库配置信息连接数据库,并通过特定的 SQL 语句查询出表信息和字段信息

以下面的 SQL 为例,只需要获取数据库连接信息,即可获取所有的表名

SELECT
	table_name 
FROM
	information_schema.TABLES 
WHERE
	table_schema = "school-miao-demo"  # 库名
	AND table_type = "base table";

# 结果
# schools
SELECT
	column_name,
	data_type,
	column_comment,
	numeric_precision,
	numeric_scale,
	character_maximum_length,
	is_nullable nullable 
FROM
	information_schema.COLUMNS 
WHERE
	table_name = 'schools'                   # 表明
	AND table_schema = 'school-miao-demo';   # 库名

# 结果为
# column_name  data_type
# sname	       varchar

得到表字段的类型后存储至配置中心即可,其中的关键一点在于需要注意数据类型的映射,比如varchar映射Stringint映射Integer等等,同时把表字段通过字符串处理工具类转化为驼峰类型(固定类型)的展示方式,例如:s_id 转换成 sid,这一点上需要注意数据库字段的设计规范

这一块的核心代码如下:

/**
 * 获取所有表信息
 *
 * @return
 */
public static List<ClassInfo> getClassInfoList() {
    if (CollectionUtil.isEmpty(CLASS_INFO_LIST)) {
        synchronized (ClassInfoFactory.class) {
            if (CollectionUtil.isEmpty(CLASS_INFO_LIST)) {
                // 获取配置项
                ConfigInfo configInfo = GlobalConfig.getConfigInfo();
                String dataBaseName = getDataBaseName(configInfo.getDatabase().getUrl());

                try {
                    List<String> tableNames = DataBaseUtil.getAllTableNames(dataBaseName);
                    String tableName = configInfo.getTableNames();
                    if (!WILDCARD.equalsIgnoreCase(tableName)) {
                        // 加载指定表
                        String[] tableArray = tableName.split(COMMA);
                        for (String table : tableArray) {
                            if (!tableNames.contains(table)) {
                                log.error("tableName [{}] does not find in the database", table);
                                throw new BizException(200, "配置文件中配置的 [" + table + "] 在数据库中未找到");
                            }
                            ClassInfo classInfo = DataBaseUtil.parseClassInfo(table, dataBaseName, configInfo.getTablePrefix());
                            CLASS_INFO_LIST.add(classInfo);
                        }
                    } else {
                        for (String table : tableNames) {
                            ClassInfo classInfo = DataBaseUtil.parseClassInfo(table, dataBaseName, configInfo.getTablePrefix());
                            CLASS_INFO_LIST.add(classInfo);
                        }
                    }
                } catch (SQLException e) {
                    throw new BizException(200, e.getMessage());
                }
            }
        }
    }
    return CLASS_INFO_LIST;
}

具体代码可以文末链接自取~

基于模板生成文件

本项目中最关键的一点就在这里。如果想实现可配置化的代码生成器,一定要有一个前提即:配置本身!在这里,我用的是 FreeMark模板引擎,它的 freemarker.template.Template#process(java.lang.Object, java.io.Writer)方法可以通过指定自定义的模板文件(FTL)、Java 实体、目标文件的方式,来帮助我们实现内容的填充,使用方法如下

<#if prefix == "*">
package ${packageName}.web.${classInfo.modelName};

import ${packageName}.core.${classInfo.modelName}.scene.${classInfo.className}SceneService;
<#else>
package ${packageName}.web.${prefix}.${classInfo.modelName};

import ${packageName}.core.${prefix}.${classInfo.modelName}.scene.${classInfo.className}SceneService;
</#if>
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
* @Author: ${author}
* @CreateTime: ${.now?string('yyyy/MM/dd HH:mm')}
* @Description: ${classInfo.classComment?replace('表$', '')}控制层
* @Version: 1.0
*/
@RestController
@RequestMapping(value = "/api/v1/${classInfo.modelName}")
@Api(tags = "${classInfo.classComment?replace('表$', '')}")
public class ${classInfo.className}Controller {

    @Resource
    private ${classInfo.className}SceneService ${classInfo.modelName}SceneService;

}

上面代码中用 ${}包裹起来的即为我们之前获取的配置文件信息,已经被加载到了我们的 FreeMark 中了

其实进行到这里,生成 DDD 架构的低代码生成器就已经实现了。不过考虑到未来博主可能更多的是写 MVC 的项目,于是基于 Mybatis-plus 的 MVC 低代码生成器也完成了。只需要在使用的时候使用StrategyTypeEnum枚举类来切换即可

public static void main(String[] args) {
        GenerateStrategy strategy = GenerateStrategyFactory.getStrategy(StrategyTypeEnum.MVC.getType());
        strategy.execute();
    }

实际演示

为了方便大家的使用,博主提供了两种使用方式。下面,我分别介绍下

基于配置文件 yml 的使用

使用这种方式时,直接在配置文件中定义相关属性即可。默认配置都以写好,配置下作者、数据库连接、生成代码包名即可完成

public class GenerateCodeApplication {
    public static void main(String[] args) {
        // 方式一:启用配置文件
        GenerateStrategy strategy = GenerateStrategyFactory.getStrategy(StrategyTypeEnum.MVC.getType());
        strategy.execute();
    }
}

这种方式,适合于直接将本项目作为您项目的一个子模块来使用。实现更加的优雅,方便

当然,有人说,那我想强解耦,不想让我的项目多一个子模块,可以实现吗?当然也是可以的。我们可以使用 pom.xml 依赖的方式

基于 pom.xml 依赖使用

在使用这种方式之前,需要先 mvc clean install将本项目安装到本地仓库中,然后通过以下坐标即可引入

<dependency>
    <groupId>com.zhanggroupId>
    <artifactId>code-generateartifactId>
    <version>1.0-SNAPSHOTversion>
dependency>

在使用 pom 依赖的情况下,原先的配置文件就不可用了。这里笔者提供了第二种方式,通过测试类加载所需配置属性

/**
 * @Description
 * @Author Mr.Zhang
 * @Date 2025/6/4 21:27
 * @Version 1.0
 */
public class codeGenerateTest {

    public static void main(String[] args) {
        // 方式二:手动设置
        Database database = new Database();
        database.setDriver("com.mysql.cj.jdbc.Driver");
        database.setUrl("");
        database.setUsername("");
        database.setPassword("");

        ConfigInfo configInfo = new ConfigInfo();
        configInfo.setDatabase(database);
        configInfo.setTableNames("*");  // * 代表生成数据库所有表,默认为 *
        configInfo.setAuthor("zhanggongming");  // 作者信息
        configInfo.setPrefix("*");  // 自定义路径前缀,eg: 我想生成的文件在 com.zhang.system,此处就填写 system。默认为 *,代表直接生成在 packageName 目录下
        configInfo.setTablePrefix("*");  // 自定义表名前缀
        configInfo.setPackageName("com.zhang");  //  生成代码 package 包名
        configInfo.setOnlyResource(false);  // 是否只生成资源层
        configInfo.setProjectPath(System.getProperty("user.dir"));

        GlobalConfig.setConfigInfo(configInfo);

        GenerateStrategy strategy = GenerateStrategyFactory.getStrategy(StrategyTypeEnum.DDD.getType());
        strategy.execute();
    }
}

这种方式就是看上去不够美观,不过别怕。基本的配置我也都以配好,其实您也只需要改部分属性就好了

总结

本项目主要的核心即两个通过mysql内置的表字段查询配合FreeMaker模板,构建具有一定规律性,通用的代码内容,技术要点包括但不限于:

  • FreeMaker
  • logback日志
  • SpringBoot

项目源码传送门:低代码生成器

如果对您有帮助的话,跪求一个 star。您的三连和star是笔者前进的动力~


你可能感兴趣的:(手把手带你实现一个DDD与MVC架构代码生成器)