spring cloud+vue線上視訊網站 7.程式碼生成模組 part 1

Mosiclone發表於2020-11-15

這一章主要是完成程式碼生成的模組,用到的工具是 freemaker ,在完成程式碼生成模組的同時會完成小節部分的程式碼。

freemaker 的基本用法

一般來說一個 freemaker 的模版基本會包括下面四部分。
文字:直接輸出的部分
註釋:<#–2333–>格式部分,不會輸出
插值:即${…}或#{…}格式的部分,將使用資料模型中的部分替代輸出
FTL指令:FreeMarker指定,和HTML標記類似,名字前加#予以區分,不會輸出

引入 freemaker

首先在 course 的依賴管理中新增 freemaker 依賴:

<dependency>
	<groupId>org.freemarker</groupId>
	<artifactId>freemarker</artifactId>
	<version>2.3.29</version>
</dependency>

然後在 course 中新建 generator 包作為程式碼生成模組,pom 檔案中新增 freemaker 依賴。

<dependency>
	<groupId>org.freemarker</groupId>
	<artifactId>freemarker</artifactId>
</dependency>

資料庫持久層生成

mabatis generator 生成持久層

修改 server 中 resource 的 generatorConfig 最後的 tableName 和 domainObjectName 。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
        <!-- 新增後sql語句中的關鍵字和表用``區分-->
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!--  生成xml檔案自動覆蓋之前的檔案  -->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
        <!--  生成例項類的tostring方法   -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <!--  刪除所有註釋-->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--連線資料庫,修改自己的資料庫名使用者名稱密碼-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/course"
                        userId="root"
                        password="czj666666">
        </jdbcConnection>

        <!-- domain類的位置 -->
        <javaModelGenerator targetProject="src/main/java"
                            targetPackage="com.course.server.domain"/>

        <!-- mapper xml的位置 -->
        <sqlMapGenerator targetProject="src/main/resources"
                         targetPackage="mapper"/>

        <!-- mapper類的位置 -->
        <javaClientGenerator targetProject="src/main/java"
                             targetPackage="com.course.server.mapper"
                             type="XMLMAPPER" />


<!--        <table tableName="test" domainObjectName="Test"></table>-->
<!--        <table tableName="chapter" domainObjectName="Chapter"></table>-->
        <table tableName="section" domainObjectName="Section"></table>
    </context>
</generatorConfiguration>

執行 mybatis-generator 後生成持久層程式碼。

生成資料庫表

這裡用之前的 doc/db/all.sql 來生成資料庫表,並新增資料。

drop table if exists `section`;
create table `section` (
    `id` char(8) not null default '' comment 'ID',
    `title` varchar(50) not null comment '標題',
    `course_id` char(8) comment '課程|course.id',
    `chapter_id` char(8) comment '大章|chapter.id',
    `video` char(200) comment '視訊',
    `time` int comment '時長|單位秒',
    `charge` char(1) comment '收費|C 收費;F 免費',
    `sort` int comment '順序',
    `created_at` DATETIME(3) comment '建立時間',
    `updated_at` datetime(3) comment '修改時間',
    primary key (`id`)
)engine=innodb default charset=utf8mb4 comment='小節';

insert into `section` (id, title, course_id, chapter_id, video, time, charge, sort, created_at, updated_at)
values ('00000001','測試小節01','00000001','00000000','',500,'F',1,now(),now());

控制層服務層模版檔案

freemaker 工具類

在 generator 模組中,建立如下結構的包。ftl 包存放 freemaker 模版檔案,控制層和服務層程式碼都由這裡的 ftl 模版生成;util 包存放 freemaker 工具類,server 中的 main 方法會呼叫抽離的工具類;server 中包含生成程式碼的 main 方法。

在這裡插入圖片描述

這裡首先建立了一個 freemaker 的工具類。其中包含了 init 方法和 generator 方法。
其中 init 方法的作用是初始化 freemaker 設定,載入模版檔案;generatro 方法的作用是生成檔案並寫入 map 對應的程式碼。

package com.course.generator.util;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;

public class FreemarkerUtil {

    static Template temp;

    public static void initConfig(String ftlPath,String ftlName) throws IOException {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
        cfg.setDirectoryForTemplateLoading(new File(ftlPath));
        cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_29));
        temp = cfg.getTemplate(ftlName);
    }

    public static void generator(String fileName, Map<String, Object> map) throws IOException, TemplateException {
        FileWriter fw = new FileWriter(fileName);
        BufferedWriter bw = new BufferedWriter(fw);
        temp.process(map, bw);
        bw.flush();
        fw.close();
    }
}

控制層模板檔案

模板檔案有對應控制層和服務層的,這裡用 ${domain} 代替 chapter ,用 ${Domain} 代替 Chapter ,在下面生成小節程式碼時分別用 section 和 Section 代替。

這裡沒有解決校驗和文字的問題。
controller.ftl

package com.course.${module}.controller.admin;

import com.course.server.dto.${Domain}Dto;
import com.course.server.dto.PageDto;
import com.course.server.dto.ResponseDto;
import com.course.server.service.${Domain}Service;
import com.course.server.util.ValidatorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/admin/${domain}")
public class ${Domain}Controller {
    private static final Logger LOG = LoggerFactory.getLogger(${Domain}Controller.class);
    public static final String BUSINESS_NAME = "${tableNameCn}";

    @Resource
    private ${Domain}Service ${domain}Service;

    @PostMapping("/list")
    public ResponseDto list(@RequestBody PageDto pageDto){
        ${domain}Service.list(pageDto);
        ResponseDto responseDto = new ResponseDto();
        responseDto.setContent(pageDto);
        return responseDto;
    }

    @PostMapping("/save")
    public ResponseDto save(@RequestBody ${Domain}Dto ${domain}Dto){
        // 儲存校驗


        ${domain}Service.save(${domain}Dto);
        ResponseDto responseDto = new ResponseDto();
        responseDto.setContent(${domain}Dto);
        return responseDto;
    }

    @DeleteMapping("/delete/{id}")
    public ResponseDto delete(@PathVariable String id){
        ${domain}Service.delete(id);
        ResponseDto responseDto = new ResponseDto();
        return responseDto;
    }
}

服務層模板檔案

service.ftl

package com.course.server.service;

import com.course.server.domain.${Domain};
import com.course.server.domain.${Domain}Example;
import com.course.server.dto.${Domain}Dto;
import com.course.server.dto.PageDto;
import com.course.server.mapper.${Domain}Mapper;
import com.course.server.util.CopyUtil;
import com.course.server.util.UuidUtil;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

import com.github.pagehelper.PageHelper;
import org.springframework.util.StringUtils;


@Service
public class ${Domain}Service {

    @Resource
    private ${Domain}Mapper ${domain}Mapper;

    public void list(PageDto pageDto){
        PageHelper.startPage(pageDto.getPage(),pageDto.getSize());

        ${Domain}Example ${domain}Example = new ${Domain}Example();
        List<${Domain}> ${domain}List = ${domain}Mapper.selectByExample(${domain}Example);

        PageInfo<${Domain}> pageInfo = new PageInfo<>(${domain}List);
        pageDto.setTotal(pageInfo.getTotal());
        List<${Domain}Dto> ${domain}DtosList;
        ${domain}DtosList = CopyUtil.copyList(${domain}List,${Domain}Dto.class);
        pageDto.setList(${domain}DtosList);
    }

    public void save(${Domain}Dto ${domain}Dto){
        ${Domain} ${domain} = CopyUtil.copy(${domain}Dto,${Domain}.class);
        if(StringUtils.isEmpty(${domain}.getId())){
            this.insert(${domain});
        }else{
            this.update(${domain});
        }
    }

    private void insert(${Domain} ${domain}){
        ${domain}.setId(UuidUtil.getShortUuid());
        ${domain}Mapper.insert(${domain});
    }

    private void update(${Domain} ${domain}){
        ${domain}Mapper.updateByPrimaryKey(${domain});
    }

    public void delete(String id){
        ${domain}Mapper.deleteByPrimaryKey(id);
    }
}

dto 層程式碼生成

要生成 dto 層的程式碼,首先要去資料庫讀取其中含有的欄位等資訊,然後將取得的資訊進行處理,最後呼叫 generator 中的方法生成程式碼。

新建 field 類

其中,新建了 field 類來儲存各個欄位的資訊:

package com.course.generator.util;

public class Field {
    private String name; // 欄位名:course_id
    private String nameHump; // 欄位名小駝峰:courseId
    private String nameBigHump; // 欄位名大駝峰:CourseId
    private String nameCn; // 中文名:課程
    private String type; // 欄位型別:char(8)
    private String javaType; // java型別:String
    private String comment; // 註釋:課程|ID

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameHump() {
        return nameHump;
    }

    public void setNameHump(String nameHump) {
        this.nameHump = nameHump;
    }

    public String getNameBigHump() {
        return nameBigHump;
    }

    public void setNameBigHump(String nameBigHump) {
        this.nameBigHump = nameBigHump;
    }

    public String getNameCn() {
        return nameCn;
    }

    public void setNameCn(String nameCn) {
        this.nameCn = nameCn;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public String getJavaType() {
        return javaType;
    }

    public void setJavaType(String javaType) {
        this.javaType = javaType;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Field{");
        sb.append("name='").append(name).append('\'');
        sb.append(", nameHump='").append(nameHump).append('\'');
        sb.append(", nameBigHump='").append(nameBigHump).append('\'');
        sb.append(", nameCn='").append(nameCn).append('\'');
        sb.append(", type='").append(type).append('\'');
        sb.append(", javaType='").append(javaType).append('\'');
        sb.append(", comment='").append(comment).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

新建 DbUtil 類

接著新建了 DbUtil 類,來連結資料庫,取到資料庫表的各個欄位並處理。

package com.course.generator.util;

//import com.course.generator.enums.EnumGenerator;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DbUtil {

    public static Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/course?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai";
            String user = "root";
            String pass = "czj666666";
            conn = DriverManager.getConnection(url, user, pass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 獲得表註釋
     * @param tableName
     * @return
     * @throws Exception
     */
    public static String getTableComment(String tableName) throws Exception {
        Connection conn = getConnection();
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select table_comment from information_schema.tables Where table_name = '" + tableName + "'");
        String tableNameCH = "";
        if (rs != null) {
            while(rs.next()) {
                tableNameCH = rs.getString("table_comment");
                break;
            }
        }
        rs.close();
        stmt.close();
        conn.close();
        System.out.println("表名:" + tableNameCH);
        return tableNameCH;
    }

    /**
     * 獲得所有列資訊
     * @param tableName
     * @return
     * @throws Exception
     */
    public static List<Field> getColumnByTableName(String tableName) throws Exception {
        List<Field> fieldList = new ArrayList<>();
        Connection conn = getConnection();
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("show full columns from `" + tableName + "`");
        if (rs != null) {
            while(rs.next()) {
                String columnName = rs.getString("Field");
                String type = rs.getString("Type");
                String comment = rs.getString("Comment");
                String nullAble = rs.getString("Null"); //YES NO
                Field field = new Field();
                field.setName(columnName);
                field.setNameHump(lineToHump(columnName));
                field.setNameBigHump(lineToBigHump(columnName));
                field.setType(type);
                field.setJavaType(DbUtil.sqlTypeToJavaType(rs.getString("Type")));
                field.setComment(comment);
                if (comment.contains("|")) {
                    field.setNameCn(comment.substring(0, comment.indexOf("|")));
                } else {
                    field.setNameCn(comment);
                }
                fieldList.add(field);
            }
        }
        rs.close();
        stmt.close();
        conn.close();
        System.out.println("列資訊:" + fieldList);
        return fieldList;
    }

    /**
     * 下劃線轉小駝峰
     */
    public static String lineToHump(String str){
        Pattern linePattern = Pattern.compile("_(\\w)");
        str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while(matcher.find()){
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 下劃線轉大駝峰
     */
    public static String lineToBigHump(String str){
        String s = lineToHump(str);
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }

    /**
     * 資料庫型別轉為Java型別
     */
    public static String sqlTypeToJavaType(String sqlType) {
        if (sqlType.toUpperCase().contains("varchar".toUpperCase())
                || sqlType.toUpperCase().contains("char".toUpperCase())
                || sqlType.toUpperCase().contains("text".toUpperCase())) {
            return "String";
        } else if (sqlType.toUpperCase().contains("datetime".toUpperCase())) {
            return "Date";
        } else if (sqlType.toUpperCase().contains("int".toUpperCase())) {
            return "Integer";
        } else if (sqlType.toUpperCase().contains("long".toUpperCase())) {
            return "Long";
        } else if (sqlType.toUpperCase().contains("decimal".toUpperCase())) {
            return "BigDecimal";
        } else {
            return "String";
        }
    }
}

新建 dto 模板

最後新建了 dto 的模板檔案。

package com.course.server.dto;

<#list typeSet as type>
<#if type=='Date'>
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
</#if>
<#if type=='BigDecimal'>
import java.math.BigDecimal;
</#if>
</#list>

public class ${Domain}Dto {

    <#list fieldList as field>
    /**
     * ${field.comment}
     */
    <#if field.javaType=='Date'>
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    </#if>
    private ${field.javaType} ${field.nameHump};

    </#list>
    <#list fieldList as field>
    public ${field.javaType} get${field.nameBigHump}() {
        return ${field.nameHump};
    }

    public void set${field.nameBigHump}(${field.javaType} ${field.nameHump}) {
        this.${field.nameHump} = ${field.nameHump};
    }

    </#list>

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        <#list fieldList as field>
        sb.append(", ${field.nameHump}=").append(${field.nameHump});
        </#list>
        sb.append("]");
        return sb.toString();
    }

}

生成方法

這裡首先去讀取 generatorConfig.xml 中修改的 tableName 和 domainObjectName ,接著呼叫 DbUtil 中的方法讀取處理各個欄位,最後分別生成各個模組的程式碼。

package com.course.generator.server;

import com.course.generator.util.DbUtil;
import com.course.generator.util.Field;
import com.course.generator.util.FreemarkerUtil;
import freemarker.template.TemplateException;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class ServerGenerator {

    static String MODULE = "business";
    static String ftlPath = "/Users/chenzhijie/Documents/course/generator/src/main/java/com/course/generator/ftl/";
    static String toDtoPath = "/Users/chenzhijie/Documents/course/server/src/main/java/com/course/server/dto/";
    static String toServicePath = "/Users/chenzhijie/Documents/course/server/src/main/java/com/course/server/service/";
    static String toControllerPath = "/Users/chenzhijie/Documents/course/" + MODULE +
            "/src/main/java/com/course/" + MODULE + "/controller/admin/";
    static String generatorConfigPath = "/Users/chenzhijie/Documents/course/server/src/main/resources/generator/generatorConfig.xml";

    public static void main(String[] args) throws Exception {
        String module = MODULE;

        // 只生成配置檔案中的第一個table節點
        File file = new File(generatorConfigPath);
        SAXReader reader=new SAXReader();
        //讀取xml檔案到Document中
        Document doc=reader.read(file);
        //獲取xml檔案的根節點
        Element rootElement=doc.getRootElement();
        //讀取context節點
        Element contextElement = rootElement.element("context");
        //定義一個Element用於遍歷
        Element tableElement;
        //取第一個“table”的節點
        tableElement=contextElement.elementIterator("table").next();
        String Domain = tableElement.attributeValue("domainObjectName");
        String tableName = tableElement.attributeValue("tableName");
        String tableNameCn = DbUtil.getTableComment(tableName);
        String domain = Domain.substring(0, 1).toLowerCase() + Domain.substring(1);
        System.out.println("表:"+tableElement.attributeValue("tableName"));
        System.out.println("Domain:"+tableElement.attributeValue("domainObjectName"));

        List<Field> fieldList = DbUtil.getColumnByTableName(domain);
        Set<String> typeSet = getJavaTypes(fieldList);
        Map<String, Object> map = new HashMap<>();
        map.put("Domain", Domain);
        map.put("domain", domain);
        map.put("tableNameCn", tableNameCn);
        map.put("module", module);
        map.put("fieldList", fieldList);
        map.put("typeSet", typeSet);

        //service層程式碼生成
        FreemarkerUtil.initConfig(ftlPath, "service.ftl");
        FreemarkerUtil.generator(toServicePath + Domain + "Service.java", map);

        //controller層程式碼生成
        FreemarkerUtil.initConfig(ftlPath, "controller.ftl");
        FreemarkerUtil.generator(toControllerPath + Domain + "Controller.java", map);

        //dto層程式碼生成
        FreemarkerUtil.initConfig(ftlPath,"dto.ftl");
        FreemarkerUtil.generator(toDtoPath + Domain + "Dto.java", map);
    }
    /**
     * 獲取所有的Java型別,使用Set去重
     */
    private static Set<String> getJavaTypes(List<Field> fieldList) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < fieldList.size(); i++) {
            Field field = fieldList.get(i);
            set.add(field.getJavaType());
        }
        return set;
    }
}

相關文章