不知各位看官在工作之中有没有陷入过疯狂CV代码、看着密密麻麻的类不想动手,或者把大把的时间花费在底层的情况。以笔者为例,会经常遇到以下两个问题:
很多时候甚至会在复制粘贴代码时漏掉一些关键的注解,比如:@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
映射String
,int
映射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();
}
为了方便大家的使用,博主提供了两种使用方式。下面,我分别介绍下
使用这种方式时,直接在配置文件中定义相关属性即可。默认配置都以写好,配置下作者、数据库连接、生成代码包名即可完成
public class GenerateCodeApplication {
public static void main(String[] args) {
// 方式一:启用配置文件
GenerateStrategy strategy = GenerateStrategyFactory.getStrategy(StrategyTypeEnum.MVC.getType());
strategy.execute();
}
}
这种方式,适合于直接将本项目作为您项目的一个子模块来使用。实现更加的优雅,方便
当然,有人说,那我想强解耦,不想让我的项目多一个子模块,可以实现吗?当然也是可以的。我们可以使用 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模板,构建具有一定规律性,通用的代码内容,技术要点包括但不限于:
项目源码传送门:低代码生成器
如果对您有帮助的话,跪求一个 star。您的三连和star是笔者前进的动力~