上一篇,我大概的介绍了一下如何基于Mybatis-Plus做自己的代码生成模板。快速上手
在这期间呢,今天是五月二十一号,我也把代码生成完善了一下,实现了添加、修改、删除、批量删除、分页查找、id查找这6个功能的代码自动生成。其中也包括VO
类、Form
类这些用于返回、接收数据的类的生成。此外,还支持单表父子关系的代码生成(例如菜单表通过parent_id
来区分父子关系)
那么下面就一起看看我是如何实现的吧。
其实,思路就是我上一篇介绍的,把自己需要的数据填进去就行了,然后在模板中使用。
但是,我在做VO、Form类的生成的时候遇到了一些问题。
因为Mybatis-Plus的默认包路径只有:controller、service、serviceImpl、entity、mapper、xml和一个other的包路径,在配置时也只能指定这几个路径。
最开始,我的想法是,用other这个包路径指定为VO和Form的输出路径,但是这样写的话,VO和Form都在一个包下面了,这肯定不行。最终效果应该是这样的:
它们应该在不同的包下面,
解决思路有两种:
domain
,form和vo包同样需要做处理两种方式本质上没区别,我用的方式二。
那么我们如何对form、vo包处理呢?这里需要介绍plus的outputCustomFile
方法。
该方法位于AbstractTemplateEngine
抽象类下,所有的模板引擎都实现了这个类:
该方法源码:
/**
* 输出自定义模板文件
*
* @param customFile 自定义配置模板文件信息
* @param tableInfo 表信息
* @param objectMap 渲染数据
* @since 3.5.1
*/
protected void outputCustomFile(@NotNull Map<String, String> customFile, @NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
String entityName = tableInfo.getEntityName();
String otherPath = getPathInfo(OutputFile.other);
customFile.forEach((key, value) -> {
String fileName = String.format((otherPath + File.separator + entityName + File.separator + "%s"), key);
outputFile(new File(fileName), objectMap, value, getConfigBuilder().getInjectionConfig().isFileOverride());
});
}
用于输出自定义文件,看到String fileName =
这句代码了吗?它就是在设置文件的输出位置。
所以我们的vo、form单独分包,就需要在这里做手脚了。
之前,我们已经写了一个类MyVelocityTemplateEngine
继承了VelocityTemplateEngine
,也就是间接继承了AbstractTemplateEngine
抽象类,所以在MyVelocityTemplateEngine
中重写这个方法就好了。
万事具备,只欠东风。不知道你有没有发现该方法的注释写的是:输出自定义模板文件,
对的,这个方法是处理自定义模板并指定生成文件的输出路径。那么什么是自定义模板?
plus默认只会找:controller、service、serviceImpl、entity、mapper、xml这几个模板文件,其它的就叫自定义模板了。
所以,要想让这个方法执行,我们首先要想办法告诉plus我们的自定义模板在哪。好在,plus为我们提供了实现:
我们需要使用注入配置,以前我一直不知道注入配置是干啥的,现在明白了。
在代码生成配置中,加上这样的代码:
// 首先自定义vo、form的模板路径
Map<String, String> customFileMap = new HashMap();
customFileMap.put("Form", "/templates/form.java.vm");
customFileMap.put("VO", "/templates/vo.java.vm");
// 注入
.injectionConfig(builder -> {
builder..customFile(customFileMap);
})
现在,outputCustomFile
方法就会执行了,现在重写这个方法吧:
@Override
protected void outputCustomFile( Map<String, String> customFile, TableInfo tableInfo, Map<String, Object> objectMap) {
String entityName = tableInfo.getEntityName();
String otherPath = getPathInfo(OutputFile.other);
customFile.forEach((key, value) -> {
String fileName = "";
if ("Form".equals(key)) {
fileName = String.format((otherPath + File.separator + "form" + File.separator + entityName + "%s" + suffixJavaOrKt()), key);
}
else {
fileName = String.format((otherPath + File.separator + "vo" + File.separator + entityName + "%s" + suffixJavaOrKt()), key);
}
outputFile(new File(fileName), objectMap, value, getConfigBuilder().getInjectionConfig().isFileOverride());
});
}
该方法会接收自定义的模板文件map,所以在定义fileName的时候判断一下map的key,为form就在路径中加上form,vo的话就加vo。
这样我们的自定义文件就能生成到指定位置了。
注入配置中,也可以用一个map去添加自定义数据,比如我的:
// 自定义数据
Map<String, Object> customDataMap = new HashMap();
// 数据校验分组
customDataMap.put("addGroup", "com.monkeylessey.group.AddGroup");
customDataMap.put("updateGroup", "com.monkeylessey.group.UpdateGroup");
// 统一响应类
customDataMap.put("responseDataName", "ResponseData");
customDataMap.put("responseDataPath", "com.monkeylessey.response.ResponseData");
// 注入
.injectionConfig(builder -> {
builder.customMap(customDataMap)
.customFile(customFileMap);
})
我们在模板中就能直接使用自定义的数据了。
之前一篇文章,我说的是重写write方法然后加入自定义数据,当然两种方式都可以的。
自定义Map这种方式适用于死数据,不需要处理。
而重写write方法这种方式适用于定义需要处理plus自带数据才能得到的数据。比如依赖注入的对象名首字母小写,这种在配置map时是无法处理的,因为plus的默认数据都还没有。这样的例子还有路径、表字段信息等等。
因为自定义数据,一般比较多。所以建议大家写不同的类去处理不同的自定义数据,这样代码不冗长也便于维护。(在write方法中)比如我定义了FormHandler去处理form的数据,VoHandler去处理vo的数据。
@Override
public void writer(Map<String, Object> objectMap, String templatePath, File outputFile) throws Exception {
// 获取table
TableInfo tableInfo = (TableInfo) objectMap.get("table");
// 取出Service接口的名字,进行首字母小写处理
String serviceNameFirstWord = tableInfo.getServiceName().substring(0,1).toLowerCase();
String serviceNameFirstWordToLower = serviceNameFirstWord + tableInfo.getServiceName().substring(1);
objectMap.put("serviceNameFirstWordToLower", serviceNameFirstWordToLower);
// 取出mapper接口的名字,进行首字母小写处理
String mapperNameFirstWord = tableInfo.getMapperName().substring(0, 1).toLowerCase();
String mapperNameFirstWordToLower = mapperNameFirstWord + tableInfo.getMapperName().substring(1);
objectMap.put("mapperNameFirstWordToLower", mapperNameFirstWordToLower);
// 对Form、VO文件的处理
new FormHandler().handler(objectMap);
super.writer(objectMap, templatePath, outputFile);
}
模板中的配置,简单了解一下语法即可,参考plus自带的,很容易上手。这里就不叙述了。
先看下生成的目录结构:
controller:
service:
entity:
form:
图太多占篇幅,就放几张代表的吧。
目前生成的代码没有错误,只不过测试得比较少。因为现在的局限性(只能单表生成代码),所以问题应该是没什么大问题了,后期表多了磨合磨合就差不多了。
现在代码生成只是后端使用main方法执行,后面需要做前端(快了),要把很多参数提取出来供前端设置。
目前只能单表确实局限性很大,我也研究了基于plus的机制能不能实现一对多、一对一这种关系,答案是能,大致思路:如果你一次生成多个表,plus是循环生成代码的,所以表A是拿不到表B的信息的。如果A包含B,那么先生成B的信息。将b的数据保存到中间层如redis或者ThreadLocal中并且需要判定哪次是存哪次是取,要保证生成A的时候能拿到B的数据)我目前还不确定能不能自己调用plus的查询方法查到表数据,可以的话就很方便了。
总之,代码生成将告一段落了。虽然有局限性,但是我估算了一下一个表能省半小时吧,摸鱼它不香吗哈哈。而且一对一、一对多修改起来也不是很麻烦了。
如果你有什么疑问可以私信我。