天天看點

擴充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>
           

代碼生成器的基本使用,請參見官網介紹資料 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。

擴充MyBatisPlus代碼生成器實作自定義前端頁面源碼生成

我這邊的開發平台開始實際使用的低版本,後來才更新到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  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());
        });
    }

}

           

繼續閱讀