当前位置:   article > 正文

扩展MyBatisPlus代码生成器实现自定义前端页面源码生成_前端代码生成器

前端代码生成器

背景

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>
  • 1
  • 2
  • 3
  • 4
  • 5

官网资料

代码生成器的基本使用,请参见官网介绍资料 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);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在哪个配置里进行扩展实现自定义的前端页面vue源码生成呢?看名字是推测不出来的,还是通过看源码获取到的,实际是在InjectionConfig里。

具体应该怎么用呢?
1.需要使用官方提供的一个自定义文件类,CustomFile,如下,指定生成的文件名、模板位置、包名(目录名)等

//自定义列表视图模板
CustomFile listViewFile=new CustomFile.Builder()
        .fileName("list.vue")
        .templatePath("/templates/list.vue.ftl")
        .enableFileOverride()
        .packageName("page")
        .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.将自定义的文件类,通过InjectionConfig的对象方法设置进去,可设置多次。

InjectionConfig injectionConfig = new InjectionConfig.Builder()              
                //自定义变量注入
                .customMap(customKeyValue)
                //自定义文件输出
                .customFile(voFile)
                .customFile(listViewFile)
                .customFile(editViewFile)
                .build();
codeGenerator.injection(injectionConfig);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

以上两步实际就完成了整体流程,涉及到一个具体的问题,即如何把模板用的数据给注入进去。原组件会自动注入大量数据,特别是数据库表和字段信息,但是我们要做自定义的前端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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202

控制生成的文件名

最后说一个使用时发现的具体问题,原代码生成组件,默认会在最终生成文件名前,默认附加实体名前缀,例如实体名是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());
        });
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/434513
推荐阅读
相关标签
  

闽ICP备14008679号