首页 > 其他分享 >SpringCloudAlibaba:从0搭建一套快速开发框架-06 告别重复代码,使用Freemarker轻松生成重复代码 - 第一节

SpringCloudAlibaba:从0搭建一套快速开发框架-06 告别重复代码,使用Freemarker轻松生成重复代码 - 第一节

时间:2025-01-18 16:28:09浏览次数:3  
标签:06 String 重复 代码 List private 生成 table public

序言:上篇主要优化完善公共模块,本篇主要创建一个生成代码的独立模块,提升开发效率,避免繁琐的重复的crud操作。由于内容较多,我就分两节写了。本节我们主要以创建项目并简单的生成数据库实体类即可,下节我们会直接搞完。

Freemarker是什么

Freemarker 是一个基于 Java 的模板引擎,它主要用于动态生成文本输出,广泛应用于 Web 应用程序、配置文件生成、代码生成等场景。它通过模板和数据模型的结合,能够将数据内容动态地插入到预定义的模板中,生成最终的输出结果。

主要特点
1. 基于模板的文本生成: Freemarker 使用模板文件,模板中包含占位符,最终的数据内容会根据模板和数据模型的匹配进行动态生成。这种方式使得生成的内容具有高度的灵活性,可以根据不同的需求生成不同的结果。
2. 与 Java 无缝集成: Freemarker 是 Java 编写的,因此它可以轻松与 Java 项目集成,并且可以通过 Java 代码将数据模型传递给模板,从而生成动态内容。
3. 高效且灵活的模板语言: Freemarker 提供了一种专门的模板语言,具有强大的功能,包括条件判断、循环、宏、内置函数等,允许在模板中进行复杂的逻辑操作。
4. 支持多种输出格式: Freemarker 可以生成多种格式的输出,例如 HTML、XML、JSON、配置文件、代码文件等。通过定义不同的模板,Freemarker 能够处理各种不同类型的输出需求。
5. 代码生成的利器: Freemarker 被广泛应用于自动化生成代码的场景,尤其在需要快速生成 CRUD 操作、API 接口文档、配置文件等时,Freemarker 可以极大提高开发效率,减少重复工作。
典型应用场景
1. Web 页面渲染: Freemarker 经常与 Web 框架(如 Spring MVC)结合使用,用于生成动态的 HTML 页面。例如,可以根据用户的输入或者从数据库中查询到的数据来生成网页内容。
2. 生成配置文件: Freemarker 可以根据传入的配置模型自动生成配置文件,如 XML 配置文件、properties 文件等。这对于一些需要根据不同环境或条件生成配置文件的场景非常实用。
3. 自动化代码生成: 通过 Freemarker,可以根据数据库表结构、配置文件或其他输入自动生成代码(如 Java 类、数据库操作代码等)。例如,使用 Freemarker 生成 Java 的实体类、DAO 层代码、Service 层代码等。
4. 电子邮件模板: Freemarker 可用于生成动态电子邮件内容,例如在发送通知邮件时,基于不同的收件人生成不同内容的邮件模板。

Freemarker 是一个功能强大的模板引擎,适用于多种场景,特别是在动态生成文本(如代码、HTML、配置文件等)时非常高效。它通过模板和数据模型的结合,提供了灵活、可扩展的输出方式,能有效提高开发效率,减少重复工作,是 Java 开发中常用的工具之一。

创建模块

介绍完之后直接进入我们最主要的代码模块。
右键父项目创建模块,不是子模块哦,如下图

注意父项选择:<None>

在这里插入图片描述

打开它的POM文件,直接放依赖

<dependencies>
   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.4</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.6</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.5</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.0.0</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

连接数据库我们只要使用的是MyBatis-Plus,所以这里也用,当然也可以用MyBatis,当然也可以不用,不用的话就需要自己创建表列集合了。

创建包com.shine.generator(本文以下全部建成父包

创建启动类GeneratorApplication,代码如下

@SpringBootApplication
public class GeneratorApplication {

    public static void main(String[] args) {
        SpringApplication.run(GeneratorApplication.class, args);
    }

}

resource下创建配置文件application.yml

generator:
  datasource:
    url: jdbc:mysql://localhost:3306/你的库?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true
    username: root
    password: 你的密码
  # 包配置
  package-config:
    modal-name: 你的模块名
    base-package: com.shine
  # 生成配置
  generator:
    database: database
    table-name:
      - u_table_name
    table-prefix: 表前缀,没有不填
    # 需要生成的
    code:
      - entity
      - enum
      - info
      - page_request
      - create_request
      - update_request
      - mapper
      - mapper_xml
      - service
      - service_impl
      - controller
    # 这里是生成的方法
    methods:
      - page
      - get
      - delete
# 数据库连接配置
spring:
  datasource:
    url: ${generator.datasource.url}
    username: ${generator.datasource.username}
    password: ${generator.datasource.password}

父包下创建包properties,并且创建类GeneratorProperties,内容如下:

@Data
@Component
@ConfigurationProperties(prefix = "generator")
public class GeneratorProperties {

    private Datasource datasource;

    private Package packageConfig;

    private Generator generator;

    @Data
    public static class Datasource {

        private String url;
        private String username;
        private String password;

    }

    @Data
    public static class Package {

        private String modalName;
        private String basePackage;

    }

    @Data
    public static class Generator {

        private String database;
        private List<String> tableName;
        private String tablePrefix;
        private List<GeneratorEnum> code;
        private List<MethodEnum> methods;

    }

}

主要是读取application.yml的配置

父包下创建包entity,创建实体类ColumnTable,内容如下

@Data
public class Column {

    private String columnName;

    private String fieldName;

    private String dataType;

    private String javaType;

    private String comment;

}
@Data
public class Table {

    private String tableName;

    private String className;

    private String lowercaseClassName;

    private String entityName;

    private String comment;

    private List<Column> columnList;

    private String generatorDate;

    private String author = "huihui";

    private String packagePath;

    private String moduleName;

}

父包下创建包model,创建类EnumModelMethodModel,内容如下

@Data
public class EnumModel {

    private String author = "huihui";

    private String packagePath;

    private String moduleName;

    private String className;

    private String generatorDate;

    private List<Item> contentList;

    @Data
    public static class Item {

        private String value;

        private String code;

        private String name;

        private String comment;

    }

}
@Data
public class MethodModel extends Table {

    private String infoName;

    private List<MethodEnum> methodList;

    private String datasourceName;

}

父包下创建包conver,创建类型转换类TypeConvert,内容如下

@Component
public class TypeConvert {

    public void convert(List<Column> columnList) {
        columnList.forEach(item -> {
            switch (item.getDataType()) {
                case "bigint":
                    item.setJavaType("Long");
                    break;
                case "varchar":
                    item.setJavaType("String");
                    break;
                case "tinyint":
                    item.setJavaType("Integer");
                    break;
                case "datetime":
                    item.setJavaType("LocalDateTime");
                    break;
                case "bit":
                    item.setJavaType("Boolean");
                case "int":
                    item.setJavaType("Integer");
                    break;
                default:
                    throw new NullPointerException("请维护类型:" + item.getDataType());
            }
        });
    }

}

父包下创建包enums,存放枚举,创建枚举GeneratorEnumMethodEnum,内容如下

public enum GeneratorEnum {

    ENTITY,
    ENUM,
    INFO,
    PAGE_REQUEST,
    CREATE_REQUEST,
    UPDATE_REQUEST,
    MAPPER,
    MAPPER_XML,
    SERVICE,
    SERVICE_IMPL,
    CONTROLLER,

}
public enum MethodEnum {

    PAGE, // 分页查询
    GET, // 详情查询
    DELETE, // 删除

}

父包下创建包mapper,创建访问数据库的接口DatabaseMapper,内容如下

@Mapper
public interface DatabaseMapper {

    Table getTableInfo(@Param("databaseName") String databaseName, @Param("tableName") String tableName);

    List<Column> getColumnInfo(@Param("databaseName") String databaseName, @Param("tableName") String tableName);

}

resource包下创建mapper包,并在mapper包下创建XML文件DatabaseMapper.xml用来编写SQL,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shine.generator.mapper.DatabaseMapper">

    <select id="getTableInfo" resultType="com.shine.generator.entity.Table">
        select table_name, table_comment as comment
        from information_schema.tables
        where table_schema = #{databaseName}
        and table_name = #{tableName}
    </select>

    <select id="getColumnInfo" resultType="com.shine.generator.entity.Column">
        select column_name, data_type, column_comment as comment
        from information_schema.columns
        where table_schema = #{databaseName}
        and table_name = #{tableName}
    </select>

</mapper>

父包下创建包handler,创建接口和抽象类,以及生成实体类的类,内容如下:

public interface GeneratorHandler {

    GeneratorEnum getEnum();

    List<String> getIgnoreColumnList();

    void handleTable(Table table);

    void handler(Table table) throws Exception;

}
@Data
@Slf4j
@Component
public abstract class AbstractGeneratorHandler implements GeneratorHandler {

    @Autowired
    private GeneratorProperties properties;

    @Override
    public List<String> getIgnoreColumnList() {
        return Collections.emptyList();
    }

    @Override
    public void handleTable(Table table) {
    }

}
@Slf4j
@Component
public class EntityHandler extends AbstractGeneratorHandler implements GeneratorHandler {

    private final GeneratorEnum generatorEnum = GeneratorEnum.ENTITY;

    @Override
    public GeneratorEnum getEnum() {
        return this.generatorEnum;
    }

    @Override
    public void handleTable(Table table) {
    }

    @Override
    public void handler(Table table) throws Exception {
        log.info("开始生成{}实体类", table.getEntityName());
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
        cfg.setClassForTemplateLoading(GeneratorApplication.class, "/templates");
        cfg.setDefaultEncoding("UTF-8");
        // 2. 加载模板
        Template template = cfg.getTemplate("entity.ftl");
        // 4. 生成文件
        File output = new File(super.getProperties().getPackageConfig().getModalName() + "/entity/" + table.getClassName() + ".java");
        File parentDir = output.getParentFile();
        if (!parentDir.exists()) {
            if (parentDir.mkdirs()) {
                log.info("成功创建目录:{}", parentDir.getAbsolutePath());
            } else {
                throw new IOException("无法创建目录:" + parentDir.getAbsolutePath());
            }
        }
        try (FileWriter writer = new FileWriter(output)) {
            template.process(table, writer);
        }
        log.info("生成{}实体类成功", table.getEntityName());
    }

}

resource包下创建包templates用来存放模板,创建模板entity.ftl内容如下

package ${packagePath}.${moduleName}.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

<#assign hasDate = columnList?filter(item -> item.javaType?contains("LocalDateTime"))>
<#if (hasDate?size > 0)>
import java.time.LocalDateTime;
</#if>

@Data
@TableName(value = "${tableName}")
public class ${className} {

<#-- 循环字段生成代码 -->
<#list columnList as column>
    /**
     * ${column.comment!""}
     */
    @TableField(value = "${column.columnName}")
    private ${column.javaType} ${column.fieldName};

</#list>
}

父包下创建包entrance,创建执行类GeneratorEntrance,内容如下

@Slf4j
@Component
public class GeneratorEntrance {

//    private final List<GeneratorHandler> handlerList = new ArrayList<>();

    @Autowired
    private GeneratorProperties properties;

    @Autowired
    private List<GeneratorHandler> handlerList;

    @Autowired
    private DatabaseMapper databaseMapper;

    @Autowired
    private TypeConvert typeConvert;

    private List<Table> tableList = new ArrayList<>();

    @PostConstruct
    public void generator() throws Exception {
        ready();
        handler();
    }

    /**
     * 准备
     */
    public void ready() {
        List<GeneratorEnum> enumList = properties.getGenerator().getCode();
        handlerList = handlerList.stream().filter(item -> enumList.contains(item.getEnum())).collect(Collectors.toList());
        log.info("查询表对象");
        List<String> tableNameList = properties.getGenerator().getTableName();
        for (String tableName : tableNameList) {
            // 查询数据库
            Table table = databaseMapper.getTableInfo(properties.getGenerator().getDatabase(), tableName);
            // 包和模块信息
            table.setPackagePath(properties.getPackageConfig().getBasePackage());
            table.setModuleName(properties.getPackageConfig().getModalName());
            this.tableNameConvert(table);
            // 转换表
            List<Column> columnList = databaseMapper.getColumnInfo(properties.getGenerator().getDatabase(), tableName);
            this.columnNameConvert(columnList);
            this.typeConvert(columnList);
            table.setColumnList(columnList);
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
            String date = formatter.format(now);
            table.setGeneratorDate(date);
            tableList.add(table);
        }
        log.info("表对象封装完成");
    }

    /**
     * 处理
     *
     * @throws Exception
     */
    public void handler() throws Exception {
        for (GeneratorHandler handler : this.handlerList) {
            for (Table table : tableList) {
                Table newTable = new Table();
                BeanUtils.copyProperties(table, newTable);
                handler.handleTable(newTable);
                handler.handler(newTable);
            }
        }
    }

    /**
     * 列类型转换
     *
     * @param columnList
     */
    protected void typeConvert(List<Column> columnList) {
        this.typeConvert.convert(columnList);
    }

    /**
     * 表名处理
     *
     * @param table
     */
    protected void tableNameConvert(Table table) {
        String tableName = table.getTableName();
        String tablePrefix = properties.getGenerator().getTablePrefix();
        if (StringUtils.isNotBlank(tablePrefix)) {
            tableName = tableName.substring(tablePrefix.length());
        }
        String className = tableName.substring(0, 1).toUpperCase() + tableName.substring(1);
        String entityName = this.toCamelCase(className);
        table.setLowercaseClassName(entityName.substring(0, 1).toLowerCase() + entityName.substring(1));

        table.setEntityName(entityName);
        table.setClassName(entityName);
    }

    /**
     * 列名处理
     *
     * @param columnList
     */
    protected void columnNameConvert(List<Column> columnList) {
        columnList.forEach(item -> {
            String fieldName = this.nameConvert(item.getColumnName());
            item.setFieldName(fieldName);
        });
    }

    private String nameConvert(String name) {
        if (name == null || name.isEmpty()) {
            throw new NullPointerException();
        }
        // 使用下划线分割字符串
        String[] parts = name.split("_");
        StringBuilder camelCaseString = new StringBuilder();
        // 遍历每一部分
        for (int i = 0; i < parts.length; i++) {
            if (i == 0) {
                // 第一个单词全部小写
                camelCaseString.append(parts[i].toLowerCase());
            } else {
                // 其它单词首字母大写
                camelCaseString.append(parts[i].substring(0, 1).toUpperCase());
                camelCaseString.append(parts[i].substring(1).toLowerCase());
            }
        }
        return camelCaseString.toString();
    }

    public String toCamelCase(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }

        StringBuilder result = new StringBuilder();
        boolean toUpperCase = false;

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == '_') {
                toUpperCase = true; // 遇到下划线,标记下一个字符需要大写
            } else {
                if (toUpperCase) {
                    result.append(Character.toUpperCase(c)); // 转为大写
                    toUpperCase = false;
                } else {
                    result.append(c); // 保留原样
                }
            }
        }

        return result.toString();
    }

}

到这里,已经可以生成实体类了(要修改配置),下节结束之后我会分享代码仓库的,我准备将这个模块单独创建一个仓库,后面也可以拿来使用

我承认这一块我的代码写的比较辣鸡,但是这里的只需要可以正常生成代码就行,所以我没有很在意,我们下期见~

标签:06,String,重复,代码,List,private,生成,table,public
From: https://blog.csdn.net/m0_56441750/article/details/145228394

相关文章

  • 科普文:算法和数据结构系列【高效的字符串检索结构:字典树Trie树原理、应用及其java示例
    概叙科普文:算法和数据结构系列【算法和数据结构概叙】-CSDN博客科普文:算法和数据结构系列【非线性数据结构:树Tree和堆Heap的原理、应用、以及java实现】-CSDN博客科普文:算法和数据结构系列【树:4叉树、N叉树】_动态维护四叉树-CSDN博客科普文:算法和数据结构系列【二叉树总结......
  • 459. 重复的子字符串
    题目这道题不会,看了卡哥思路,卡哥提供了三种方法。方法一:暴力解法自己写的代码:classSolution{public:boolrepeatedSubstringPattern(strings){intn=s.size();for(intlen=1;len<=n/2;++len){if(n%len!=0......
  • 空间计量模型,包括空间滞后模型、空间误差模型、空间杜宾模型的matlab代码
    空间计量模型,包括空间滞后模型、空间误差模型、空间杜宾模型的matlab代码资源文件列表NewElhorstPanelCode/allersdumregime.m , 7573NewElhorstPanelCode/cigardemo.wk1 , 28587NewElhorstPanelCode/cigarette.wk1 , 124885NewElhorstPanelCode/demean.m......
  • JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请
    目录JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)一、什么时候该使用Array.map(),与forEach()的区别是什么?1、什么时候该用Array.map()2、Array.map()与Array.forEach()的......
  • 代码随想录 字符串 test 6(KMP,超详细)
    28.找出字符串中第一个匹配项的下标-力扣(LeetCode)一暴力:        以主串中的每个字符为起点,每次匹配从当前主串的起点和子串的首位开始匹配:匹配成功:返回本次匹配的主串起点。匹配失败:以主串的下一个字符作为新起点,重新尝试匹配。时间复杂度为o(m*n)(m为主串长度,n......
  • RK3506到底有多香?抢先看核心板详细参数配置
    RK3506是瑞芯微Rockchip在2024年第四季度全新推出的入门级芯片平台,三核Cortex-A7+单核Cortex-M0多核异构设计,具备最高-40~85℃的工业宽温性能、发热量小,IO接口丰富,即时性高,低延迟,反应速度快等特点!触觉智能已推出RK3506核心板,抢先了解核心板详细参数配置。产品概述触觉智......
  • 机器学习基础原理————可解释性Shap Value原理及代码
    如果⼀个机器学习模型运⾏良好,为什么我们仅仅信任该模型⽽忽略为什么做出特定的决策呢?诸如分类准确性之类的单⼀指标⽆法完整地描述⼤多数实际任务。当涉及到预测模型时,需要作出权衡:你是只想知道预测是什么?例如,客户流失的概率或某种药物对病⼈的疗效。还是想知道为什么做出这样的......
  • 机器学习基础原理————贝叶斯优化原理及代码实现
    本文通过结合如下论文以及blog:1、贝叶斯优化研究综述:https://doi.org/10.13328/j.cnki.jos.005607.2、高斯回归可视化:https://jgoertler.com/visual-exploration-gaussian-processes/3、贝叶斯优化:http://arxiv.org/abs/1012.2599对贝叶斯优化进行较为全面的介绍,以及部分代......
  • 51c大模型~合集106
    我自己的原文哦~   https://blog.51cto.com/whaosoft/13115290#GPT-5、Opus3.5为何迟迟不发新猜想:已诞生,被蒸馏成小模型来卖「从现在开始,基础模型可能在后台运行,让其他模型能够完成它们自己无法完成的壮举——就像一个老隐士从秘密山洞中传递智慧。」最近几个月,......
  • 第10个项目:图片转Turtle代码生成器Python源码
    完整源码在文末,可直接下载使用,也可在此基础上做定制开发。应用场景:上传图片,自动生成Turtle代码。点击执行代码,可把图片完整画出来。功能特点:支持设置背景图片,可在背景图片上嵌入式画图,很有意思。软件截图:核心源码:importtkinterastkfromtkinterimportfiledialog,t......