赞
踩
Mybatis在开发界的地位无需多说,MyBatisPlus是站在Mybatis巨人肩膀上一个很优秀的组件,其理念是只做增强,不做改变,在大量项目中使用,提升了开发效率。
MyBatisPlus自带了一个代码生成器mybatis-plus-generator,可基于数据库库表,结合模板技术,自动生成程序源码,不过默认情况下,只支持Entity、Mapper、Service、Controller这些层次。如果想生成未预置的代码,如vo对象、前端vue页面等,需要做一些定制和扩展来实现。
首先,说下组件版本情况,不同的版本会有所差异,mybatis-plus-generator 3.5.1前的版本和之后的版本不兼容,我这边使用的MybatisPlus版本是3.5.3。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3</version>
</dependency>
代码生成器的基本使用,请参见官网介绍资料 https://www.baomidou.com/pages/779a6e/ 。
从使用角度而言,官方资料介绍得比较简要,要实际使用,仍然需要进行具体的尝试和摸索,包括必要时看下源码怎么处理的,才能发现一些限制和约束,最终理顺。
今天我重点来说说,如何在mybatis-plus-generator的基础上,来实现生成前端vue源码的目的。
官网关于该该部分的说明,地址如下:https://www.baomidou.com/pages/981406/#freemarker%E6%A8%A1%E7%89%88%E6%94%AF%E6%8C%81-dto-vo%E7%AD%89-%E9%85%8D%E7%BD%AE
看完后我是一头雾水,不知道从哪下手,然后花了比较多的时间,去翻源码,理了下整体的代码生成逻辑,最终确认应该如何自定义实现,分享出来给大家参考。
代码生成器的类有两个,一是AutoGenerator,二是FastAutoGenerator,这俩啥区别官网没提,看源码大致推测了下,前者是老版本,后者是新版本,后者在前者基础上做了一些额外工作,如支持通过控制台的模式进行交互式生成,但最终进行代码生成操作,execute方法中调用的还是AutoGenerator。
我这边的开发平台开始实际使用的低版本,后来才升级到3.5.3新版本的,并且也不需要控制台这种交互式功能,因此还是沿用使用AutoGenerator,而没使用FastAutoGenerator。
生成代码前需要进行必要的配置,主体如下:
public void generateCode(String entityCode) {
//创建代码生成器对象
AutoGenerator codeGenerator = getCodegGenerator();
//获取实体配置信息
Entity entity = entityService.getByCode(entityCode);
//获取模块配置信息
Module module = moduleService.query(entity.getModuleId());
//配置全局信息
configGlobal(codeGenerator,entity.getAuthor());
//配置包
configPackage(codeGenerator,module.getPackagePath(),module.getCode());
//配置模板
configTemplate(codeGenerator);
//配置注入
configInjection(codeGenerator,entity,module.getAppCode());
//策略配置
configStrategy(codeGenerator,entity.getCode(), module.getAbbreviation());
//使用Freemarker替代默认的Velocity模板引擎
MyFreemarkerTemplateEngine freemarkerTemplateEngine = new MyFreemarkerTemplateEngine();
//生成代码
codeGenerator.execute(freemarkerTemplateEngine);
}
在哪个配置里进行扩展实现自定义的前端页面vue源码生成呢?看名字是推测不出来的,还是通过看源码获取到的,实际是在InjectionConfig里。
具体应该怎么用呢?
1.需要使用官方提供的一个自定义文件类,CustomFile,如下,指定生成的文件名、模板位置、包名(目录名)等
//自定义列表视图模板
CustomFile listViewFile=new CustomFile.Builder()
.fileName("list.vue")
.templatePath("/templates/list.vue.ftl")
.enableFileOverride()
.packageName("page")
.build();
2.将自定义的文件类,通过InjectionConfig的对象方法设置进去,可设置多次。
InjectionConfig injectionConfig = new InjectionConfig.Builder()
//自定义变量注入
.customMap(customKeyValue)
//自定义文件输出
.customFile(voFile)
.customFile(listViewFile)
.customFile(editViewFile)
.build();
codeGenerator.injection(injectionConfig);
以上两步实际就完成了整体流程,涉及到一个具体的问题,即如何把模板用的数据给注入进去。原组件会自动注入大量数据,特别是数据库表和字段信息,但是我们要做自定义的前端vue页面,不可避免也需要再注入大量自定义的变量和数据,这是通过InjectionConfig的customMap对象设置进去的。
前端页面的文件模板,需要放到resources目录下templates目录下,如下
<template>
<el-container class="list-container">
<el-main class="main">
<collapse-tab class="query">
<el-form
:inline="true"
:model="queryCondition"
label-width="80px"
@keyup.enter.native="query"
>
<!--查询条件区 -->
<#list queryConditionList as item>
<#--根据数据类型处理-->
<#if item.dataType=="STRING">
<el-form-item label="${item.name}">
<p-input v-model="queryCondition.${item.code}" type="${item.operation}" class="form-item"/>
</el-form-item>
<#elseif item.dataType=="DATETIME">
<el-form-item label="${item.name}">
<el-date-picker
v-model="queryCondition.${item.code}BeginForQuery"
:value-format="dateFormatter.getDatetimeFormat('${item.formatPattern}')"
:type="dateFormatter.getDatetimeType('${item.formatPattern}')"
align="right"
unlink-panels
placeholder="开始"
class="form-item"
/>
</el-form-item>
<el-form-item label="至">
<el-date-picker
v-model="queryCondition.${item.code}EndForQuery"
:value-format="dateFormatter.getDatetimeFormat('${item.formatPattern}')"
:type="dateFormatter.getDatetimeType('${item.formatPattern}')"
align="right"
unlink-panels
placeholder="结束"
class="form-item"
/>
</el-form-item>
<#elseif item.dataType=="INTEGER" || item.dataType=="LONG" || item.dataType=="DOUBLE" || item.dataType=="DECIMAL" >
<el-form-item label="${item.name}">
<el-input v-model="queryCondition.${item.code}BeginForQuery" class="form-item" />
</el-form-item>
<el-form-item label="至">
<el-input v-model="queryCondition.${item.code}EndForQuery" class="form-item" />
</el-form-item>
<#elseif item.dataType=="DATA_DICTIONARY">
<p-input v-model="queryCondition.${item.code}" class="form-item" />
<#else>
<p-input v-model="queryCondition.${item.code}" class="form-item"/>
</#if>
</#list>
<el-form-item style="float:right;">
<list-button-query :page-code="pageCode"/>
</el-form-item>
<div class="clearfix"/>
</el-form>
</collapse-tab>
<el-card class="table">
<div>
<#list pageButtonList as pageButton>
<el-button
<#if pageButton.permissionFlag=="YES">
v-permission="pageCode+'${pageButton.permissionCode}'"
</#if>
type="primary"
<#if pageButton.icon??>
icon="el-icon-${pageButton.icon}"
</#if>
@click="${pageButton.functionCall}"
>${pageButton.name}</el-button>
</#list>
<div style="clear:both;"/>
</div>
<div style="margin-top:5px;">
<div>
<columns-controller v-model="columnList" :table-key="tableKey"/>
</div>
<el-table
v-loading="loading"
:data="tableData"
style="width: 100%"
highlight-current-row
border
@sort-change="handleSortChange"
@current-change="handleRowChange"
@selection-change="handleSelectionChange"
@row-dblclick="handleDoubleClickRow"
>
<el-table-column type="selection" width="55"/>
<el-table-column
v-for="(item, index) in showCols"
:key="index"
:label="item.label"
:prop="item.prop"
:show-overflow-tooltip="item.showOverflowTooltip"
:width="item.width"
:formatter="item.formatFunc"
:sortable="item.sortable"
/>
<el-table-column fixed="right" label="操作" width="250">
<template slot-scope="scope">
<#list rowButtonList as rowButton>
<el-button
<#if rowButton.permissionFlag=="YES">
v-permission="pageCode+'${rowButton.permissionCode}'"
</#if>
type="text"
@click="${rowButton.functionCall}(scope.row.id)"
>${rowButton.name}</el-button>
</#list>
<el-dropdown style="margin-left:10px;color:#1890ff">
<span class="el-dropdown-link">
更多
<i class="el-icon-arrow-down el-icon--right"/>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<table-button-enable :id="scope.row.id" :page-code="pageCode"/>
</el-dropdown-item>
<el-dropdown-item>
<table-button-disable :id="scope.row.id" :page-code="pageCode"/>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
</div>
<list-pager
:page-num="pageInfo.pageNum"
:page-size="pageInfo.pageSize"
:page-total="pageTotal"
/>
</el-card>
<detail ref="detail" @ok="handleDetailSave"/>
</el-main>
</el-container>
</template>
<script>
import {listMixin} from '@/mixin/listMixin'
import Detail from './edit'
const MODULE_CODE = '${package.ModuleName}'
const ENTITY_TYPE = '${entity?uncap_first}'
export default {
name: ENTITY_TYPE,
components: {Detail},
mixins: [listMixin],
data() {
return {
entityType: ENTITY_TYPE,
moduleCode: MODULE_CODE,
// eslint-disable-next-line no-eval
api: eval('this.$api.' + MODULE_CODE + '.' + ENTITY_TYPE),
pageCode: MODULE_CODE + ':' + ENTITY_TYPE + ':',
queryCondition: {},
columnList: [
// prop label show 必须定义, width/showOverflowTooltip/formatFunc/sortable 需要的话定义
<#list queryResultList as field>
{
prop: '${field.code}',
label: '${field.name}'
<#if field.showFlag=="YES">
, show: true
<#else>
, show: false
</#if>
<#if field.showOverflowTooltipFlag=="YES">
, showOverflowTooltip: true
<#else>
, showOverflowTooltip: false
</#if>
<#if field.width??>
, width: '${field.width}'
</#if>
<#if field.formatFunction??>
, formatFunction: '${field.formatFunction}'
</#if>
<#if field.sortableFlag=="YES">
, sortable: '${field.sortableFlag}??'
</#if>
}<#if field_has_next>, </#if>
</#list>
]
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
</style>
最后说一个使用时发现的具体问题,原代码生成组件,默认会在最终生成文件名前,默认附加实体名前缀,例如实体名是User,VO.java.ftl会自动生成UserVO.java,这样没问题。但是对于前端页面,如list.vue,edit.vue,因为是分目录放实体,希望最终文件名不变,而不要附加前缀变成UserList.vue、UserEdit.vue。
开始的时候,找了下CustomFile对象,发现没有属性进行进一步控制,然后就自己写了个类,继承于官方的FreemarkerTemplateEngine,重写了outputCustomFile方法,只对VO.java才附加实体名。
/**
* 自定义模板引擎
* 继承官方,覆写自定义输出的文件名,控制是否附加实体名前缀
* @author wqliu
* @date 2023-1-29
*/
public class MyFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
/**
* 视图对象模板文件名
*/
public static final String VO_JAVA = "VO.java";
/**
* 输出自定义模板文件
*
* @param customFiles 自定义模板文件列表
* @param tableInfo 表信息
* @param objectMap 渲染数据
* @since 3.5.3
*/
@Override
protected void outputCustomFile(@NotNull List<CustomFile> customFiles, @NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
String entityName = tableInfo.getEntityName();
String parentPath = getPathInfo(OutputFile.parent);
customFiles.forEach(file -> {
String filePath = StringUtils.isNotBlank(file.getFilePath()) ? file.getFilePath() : parentPath;
if (StringUtils.isNotBlank(file.getPackageName())) {
filePath = filePath + File.separator + file.getPackageName();
}
//只有为视图对象时,才附加实体名,对于其他模板,如前端页面,如list.vue/edit.vue,不附加实体名
String prefix= "";
if(file.getFileName().equals(VO_JAVA))
{
prefix=entityName;
}
String fileName = filePath + File.separator + prefix + file.getFileName();
outputFile(new File(fileName), objectMap, file.getTemplatePath(), file.isFileOverride());
});
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。