使用SpringBoot3打造个性化的代码生成器

代码基于SpringBoot3、Vue3、highlight实现自定义代码生成功能

SpringBoot3.x、MySQL8、MyBatisPlus3.5.x、velocity2.x、SpringSecurity6.x、Vue3、TypeScript、highlight

demo所需要的依赖及其对应版本号

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.molu</groupId>
    <artifactId>mgzyf-api</artifactId>
    <version>2.4.1</version>
    <description>Java 17 + SpringBoot3 + SpringSecurity6 </description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version> <!-- lookup parent from repository -->
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <hutool.version>5.8.15</hutool.version>
        <mysql.version>8.0.28</mysql.version>
        <druid.version>1.2.16</druid.version>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <mapstruct.version>1.5.3.Final</mapstruct.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    </properties>

    <dependencies>
        <!-- velocity -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <!--编译测试环境,不打包在lib-->
            <scope>provided</scope>
        </dependency>
        
        <!-- 允许使用Lombok的Java Bean类中使用MapStruct注解 (Lombok 1.18.20+) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>${lombok-mapstruct-binding.version}</version>
            <scope>provided</scope>
        </dependency>

        <!--    hutool工具包    -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

  <!-- web支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  
  <!-- security安全支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

  <!-- 连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

  <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!--    阿里druid工具包    -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.40</version>
        </dependency>

  <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

  <!-- 参数验证 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

  <!-- 自定义配置类支持 -->
        <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-configuration-processor</artifactId>
         <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2代码生成实现步骤

配置文件

这里是最基础的MySQL的配置信息

application

server:
  port: 8989

spring:
  jackson:
    ## 默认序列化时间格式
    date-format: yyyy-MM-dd HH:mm:ss
    ## 默认序列化时区
    time-zone: GMT+8
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      master:
        url: jdbc:mysql://127.0.0.1:3306/test01?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
        username: xxx
        password: 123
        driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 15
      min-idle: 15
      max-active: 200
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: ""
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: false
      connection-properties: false

# 配置生成代码的数据库
cn:
  molu:
    generate:
      database: test01
1.1、代码生成器源码目录

这里是代码生成器的源码目录结构,下面会逐一展示这3个类

图片

图片
1.2、自定义模板目录

这里是根据我的项目需要,自定义的代码生成器模板,下面会以Service为例解释

图片

图片
1.3、代码生成器源码

1.3.1、API接口层

提供数据到Vue页面的API接口,主要功能是将模板解析为代码返回给前台

import cn.molu.system.common.result.Result;
import cn.molu.system.generate.mapper.ColumnDetailMapper;
import cn.molu.system.generate.vo.ColumnDetailVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author 陌路
 * @apiNote 数据库表细信息
 * @date 2023/12/31 13:16
 * @tool Created by IntelliJ IDEA
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/gen")
public class ColumnDetailController {

    @Value("${cn.molu.generate.database}")
    private String database; // 这里是数据库的名字,我的数据库是 test01
    private final ColumnDetailMapper columnDetailMapper;

    /**
     * 获取所有表信息数据
     *
     * @return List<ColumnDetailVo>
     */
    @GetMapping("/getAllTables")
    @Cacheable(value = "gen:allTableDetails", key = "#root.methodName")
    public Result<List<ColumnDetailVo>> getAllTables() {
        List<ColumnDetailVo> columnDetailVoList = columnDetailMapper.getColumnDetailMapVo(database);
        return Result.success(columnDetailVoList);
    }

    /**
     * 获取所有表中字段信息数据
     *
     * @param tableName 表名
     * @return List<ColumnDetailVo>
     */
    @GetMapping("/getTableInfo/{tableName}")
    public Result<String> getTableInfo(@PathVariable String tableName,
                                       @RequestParam(value = "delPrefix", required = false) String delPrefix,
                                       @RequestParam(value = "packageName", required = false) String packageName,
                                       @RequestParam(value = "type") String type
    ) {
        List<ColumnDetailVo> columnDetailVoList = columnDetailMapper.getColumnDetailMapVoByTableName(database, tableName);
        try (StringWriter writer = new StringWriter()) {
            // 初始化Velocity引擎
            Properties p = new Properties();
            // 加载classpath目录下的vm文件
            p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            // 定义字符集
            p.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
            // 初始化Velocity引擎,指定配置Properties
            Velocity.init(p);
            // 获取模板
            Template template = Velocity.getTemplate("vm/" + type + ".vm", "UTF-8");
            ColumnDetailVo detailVo = new ColumnDetailVo();
            Map<String, Object> map = detailVo.listToMap(columnDetailVoList, delPrefix); // 去除前缀
            map.put("packageName", packageName); // 文件所在包
            // 创建Velocity上下文
            VelocityContext context = new VelocityContext();
            // 设置模板中的变量
            context.put("map", map);
            // 将模板与上下文数据进行合并
            template.merge(context, writer);
            // 输出结果
            String writerString = writer.toString();
            return Result.success(writerString);
        } catch (IOException | ResourceNotFoundException | ParseErrorException | MethodInvocationException e) {
            log.error("报错了:" + e.getMessage(), e);
        }
        return Result.failed();
    }
}

1.3.2、Mapper

查询数据库,获取表字段信息,以便后面生成代码使用

import cn.molu.system.generate.vo.ColumnDetailVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * @author 陌路
 * @apiNote 数据库表细信息
 * @date 2023/12/31 13:14
 * @tool Created by IntelliJ IDEA
 */
@Mapper
public interface ColumnDetailMapper {

    /**
     * 获取所有表信息数据
     *
     * @return List<ColumnDetailVo>
     */
    public List<ColumnDetailVo> getColumnDetailMapVo(@Param("database") String database);

    /**
     * 获取所有表中字段信息数据
     *
     * @param tableName 表名
     * @return List<ColumnDetailVo>
     */
    public List<ColumnDetailVo> getColumnDetailMapVoByTableName(@Param("database") String database, @Param("tableName") String tableName);
}

1.3.3、对应Mapper的xml

根据application中指定的数据库名,获取数据库中所有的表名以及表中的字段名

<?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="cn.molu.system.generate.mapper.ColumnDetailMapper">

    <resultMap type="cn.molu.system.generate.vo.ColumnDetailVo" id="ColumnDetailMap">
        <result property="tableName" column="table_name" jdbcType="VARCHAR"/>
        <result property="tableComment" column="table_comment" jdbcType="VARCHAR"/>
        <result property="engine" column="engine" jdbcType="VARCHAR"/>
        <result property="tableCollation" column="table_collation" jdbcType="VARCHAR"/>
        <result property="tableRows" column="table_rows" jdbcType="VARCHAR"/>
        <result property="createTime" column="create_time" jdbcType="VARCHAR"/>
    </resultMap>

    <!--  获取所有表信息数据  -->
    <select id="getColumnDetailMapVo" resultMap="ColumnDetailMap">
        select table_name, <!-- 表名 -->
               table_comment, <!-- 表名注释 -->
               engine,  <!-- 表数据引擎 -->
               table_collation,  <!-- 表字符集 -->
               table_rows,  <!-- 表中数据条数 -->
               create_time <!-- 表创建时间 -->
        from information_schema.tables
        where upper(table_schema) = upper(#{database})
    </select>

    <!--  获取所有表中字段信息数据  -->
    <select id="getColumnDetailMapVoByTableName" parameterType="string" resultMap="ColumnDetailMap">
        SELECT TABLE_SCHEMA, <!-- 数据库名 -->
               TABLE_NAME, <!-- 表名 -->
               (select b.table_comment
                  from information_schema.tables b
                 where upper(table_schema) = upper(#{database})
                   and upper(table_name) = upper(#{tableName})
                 limit 1) as TABLE_COMMENT, <!-- 表名注释 -->
               COLUMN_NAME, <!-- 字段名 -->
               COLUMN_DEFAULT, <!-- 默认值 -->
               IS_NULLABLE, <!-- 是否可为空:YES、NO -->
               DATA_TYPE, <!-- 数据类型:int、varchar... -->
               COLUMN_TYPE, <!-- 字段类型:int、varchar(30)... -->
               COLUMN_KEY, <!-- 是否主键:PRI -->
               EXTRA, <!-- 是否自增:auto_increment(自增) -->
               COLUMN_COMMENT <!-- 字段注释 -->
        FROM information_schema.columns a
        WHERE upper(table_schema) = upper(#{database})
          AND upper(table_name) = upper(#{tableName})
    </select>
</mapper>

1.3.4、各字段想起类

对应表中字段和字段信息的实体类

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author 陌路
 * @apiNote 数据库表细信息
 * @date 2023/12/31 13:21
 * @tool Created by IntelliJ IDEA
 */
@Data
@ToString
@NoArgsConstructor
@EqualsAndHashCode
@Accessors(chain = true)
@JsonInclude(value = JsonInclude.Include.NON_NULL, content = JsonInclude.Include.NON_EMPTY)
public class ColumnDetailVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 9196390045041955368L;
//    ====================查询所有表信息====================
    /**
     * 表名
     */
    private String tableName;
    /**
     * 表名注释
     */
    private String tableComment;
    /**
     * 表数据引擎
     */
    private String engine;
    /**
     * 表字符集
     */
    private String tableCollation;
    /**
     * 表中数据条数
     */
    private String tableRows;
    /**
     * 表创建时间
     */
    @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
    @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
    private String createTime;

    //        ====================查询指定表中的字段信息====================
    /**
     * 数据库名
     */
    private String tableSchema;
    /**
     * 字段名
     */
    private String columnName;
    /**
     * 默认值
     */
    private String columnDefault;
    /**
     * 是否可为空:YES、NO
     */
    private String isNullable;
    /**
     * 数据类型:int、varchar...
     */
    private String dataType;
    /**
     * 字段类型:int、varchar(30)...
     */
    private String columnType;
    /**
     * 是否主键:PRI
     */
    private String columnKey;
    /**
     * 是否自增:auto_increment(自增)
     */
    private String extra;
    /**
     * 字段注释
     */
    private String columnComment;

    /**
     * 是否主键
     *
     * @param columnKey 字段键
     * @return 是否主键
     */
    public boolean isPrimaryKey(String columnKey) {
        return StrUtil.equalsIgnoreCase(columnKey, "PRI");
    }

    /**
     * 获取主键字段名
     *
     * @param list 字段列表
     * @return 主键字段名
     */
    public String getPrimaryKey(List<ColumnDetailVo> list) {
        return Optional.ofNullable(list)
                .orElseGet(ArrayList::new)
                .stream()
                .filter(item -> isPrimaryKey(item.columnKey))
                .findFirst()
                .orElseGet(ColumnDetailVo::new).columnName;
    }

    /**
     * 列表转Map
     *
     * @param list          列表
     * @param replacePrefix 替换前缀
     * @return Map
     */
    public Map<String, Object> listToMap(List<ColumnDetailVo> list, String replacePrefix) {
        if (Objects.isNull(list) || list.isEmpty()) return null;
        Map<String, Object> map = new HashMap<>(2);
        ColumnDetailVo detailVo = list.get(0);
        String voTableName = detailVo.getTableName().toLowerCase();
        String subTableName = voTableName.replace(replacePrefix, "");
        String className = toUpperFirst(subTableName);
        map.put("tableName", voTableName); // 表名
        map.put("tableComment", detailVo.getTableComment()); // 表名注释
        map.put("className", StrUtil.upperFirst(className)); // Java类名
        map.put("subClassName", className); // Java类名
        map.put("path", voTableName.replace("_", "/")); // 生成路径
        map.put("prem", voTableName.replace("_", ":")); // 权限标识
        map.put("currTime", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN)); // 日期时间
        AtomicReference<String> pk = new AtomicReference<>();
        AtomicReference<String> pkType = new AtomicReference<>();
        AtomicReference<String> getterPk = new AtomicReference<>();
        AtomicReference<String> pkColumn = new AtomicReference<>();
        List<Map<String, String>> fields = new ArrayList<>(2);
        List<Map<String, String>> otherColumn = new ArrayList<>(2);
        list.forEach(vo -> {
            Map<String, String> field = new HashMap<>(2);
            field.put("columnName", vo.getColumnName()); // 字段名
            field.put("javaField", toUpperFirst(vo.getColumnName())); // 字段对应的Java字段
            field.put("columnComment", vo.getColumnComment()); // 字段注释
            String javaType = getJavaType(vo.getDataType());
            field.put("javaType", javaType); // java字段类型
            field.put("jdbcType", vo.getDataType().toUpperCase());
            field.put("jdbcTypeXml", getJdbcTypeXml(vo.getDataType().toLowerCase()));
            field.put("type", getType(vo.getDataType()));
            field.put("getter", StrUtil.upperFirst(toUpperFirst(vo.getColumnName())));
            if (isPrimaryKey(vo.getColumnKey())) {
                if (StrUtil.equalsIgnoreCase(javaType, "Integer")) {
                    javaType = "Long";
                }
                field.put("javaType", javaType); // java字段类型
                field.put("pkColumn", vo.getColumnName()); // 主键
                pk.set(toUpperFirst(vo.getColumnName()));
                pkColumn.set(vo.getColumnName());
                pkType.set(javaType);
                getterPk.set(StrUtil.upperFirst(toUpperFirst(vo.getColumnName())));
            } else {
                otherColumn.add(field);
            }
            fields.add(field);
        });
        map.put("columns", fields);
        map.put("otherColumn", otherColumn);
        map.put("pk", pk);
        map.put("pkColumn", pkColumn);
        map.put("pkType", pkType);
        map.put("getterPk", getterPk);
        return map;
    }

    /**
     * @title 获取字段类型
     * @author 陌路
     * @date 2023/12/31 21:01
     */
    private String getJdbcTypeXml(String dataType) {
        return switch (dataType) {
            case "int", "tinyint" -> "INTEGER";
            case "char" -> "CHAR";
            case "mediumint" -> "MEDIUMINT";
            case "bigint" -> "BIGINT";
            case "float" -> "FLOAT";
            case "double" -> "DOUBLE";
            case "bit" -> "BIT";
            case "datetime", "date", "time", "timestamp" -> "TIMESTAMP";
            default -> "VARCHAR";
        };
    }

    /**
     * @title 获取字段类型
     * @author 陌路
     * @date 2023/12/31 21:01
     */
    private String getType(String dataType) {
        String javaType = getJavaType(dataType);
        String type = "java.lang." + dataType;
        if (StrUtil.equals(javaType, "Date")) {
            type = "java.util.Date";
        }
        return type;
    }

    /**
     * @title 获取字段类型
     * @author 陌路
     * @date 2023/12/31 21:01
     */
    private String getJavaType(String dataType) {
        return switch (dataType) {
            case "bigint" -> "Long";
            case "datetime", "date", "time", "timestamp" -> "Date";
            case "decimal", "double" -> "Double";
            case "float" -> "Float";
            case "int", "tinyint", "integer" -> "Integer";
            default -> "String";
        };
    }

    /**
     * 首字母转大写
     *
     * @param field 字段
     * @return 首字母大写
     */
    public String toUpperFirst(String field) {
        // 表名转驼峰命名
        StringBuilder string = new StringBuilder();
        if (StrUtil.isNotEmpty(field) && field.contains("_")) {
            for (String str : field.split("_")) {
                string.append(StrUtil.upperFirst(str));
            }
        } else {
            string = new StringBuilder(StrUtil.upperFirst(field));
        }
        return StrUtil.lowerFirst(string.toString());
    }
}
1.4、自定义模板

自定义的模板,通过模板生成所需要的代码

package $!{map.packageName}.service;

import $!{map.packageName}.entity.$!{map.className};
import com.baomidou.mybatisplus.core.metadata.IPage;
import $!{map.packageName}.params.$!{map.className}Params;
import java.lang.*;

/**
 * @apiNote $!{map.tableComment}($!{map.className})表服务接口
 * @author 陌路
 * @date $!{map.currTime}
 * @tool Created by IntelliJ IDEA
 */
public interface $!{map.className}Service /* extends IService<$!{map.className}> */{

    /**
     * 通过ID查询单条数据
     *
     * @author 陌路
     * @date $!{map.currTime}
     * @param $!{map.pk} 主键
     * @return $!{map.subClassName} 实例对象
     */
    $!{map.className} queryById($!{map.pkType} $!{map.pk});

    /**
     * 分页查询
     *
     * @author 陌路
     * @date $!{map.currTime}
     * @param $!{map.subClassName}Params 筛选条件
     * @return IPage<$!{map.className}> 查询结果
     */
    IPage<$!{map.className}> queryByPage($!{map.className}Params $!{map.subClassName}Params);

    /**
     * 新增数据
     *
     * @author 陌路
     * @date $!{map.currTime}
     * @param $!{map.subClassName} 实例对象
     * @return $!{map.subClassName} 实例对象
     */
    $!{map.className} insert($!{map.className} $!{map.subClassName}));

    /**
     * 修改数据
     *
     * @author 陌路
     * @date $!{map.currTime}
     * @param $!{map.subClassName} 实例对象
     * @return $!{map.subClassName} 实例对象
     */
    $!{map.className} update($!{map.className} $!{map.subClassName}));

    /**
     * 通过主键作废(逻辑删除)
     *
     * @author 陌路
     * @date $!{map.currTime}
     * @param ids 主键
     * @return boolean 是否成功
     */
    boolean deleteByIds(String ids);

    /**
     * 通过主键删除(物理删除)
     *
     * @author 陌路
     * @date $!{map.currTime}
     * @param ids 主键
     * @return boolean 是否成功
     */
    boolean removeByIds(String ids);
}
1.5、页面预览代码

后台生成代码返回给vue页面,vue渲染,并使用语法高亮展示完整代码

<script setup lang="ts">
defineOptions({
  name: "Code",
  inheritAttrs: false,
});

import { ColumnDetailQuery, ColumnDetailVo } from "@/api/generate/types";
import { getAllTables, getTableInfo } from "@/api/generate";
import { ref, onMounted, reactive } from "vue";

import "highlight.js/styles/atom-one-light.css";
import "highlight.js/lib/common";
import hljs from "highlight.js";

const queryParams = reactive<ColumnDetailQuery>({});
const queryFormRef = ref(ElForm);
const dialog = reactive<DialogOption>({
  visible: false,
  title: "",
});
// 选择表格行的数据ID
const loading = ref(false); // 加载状态
const columnDetailVoList = ref<ColumnDetailVo[]>([]); // 记录列表数据
const dataType = ref(0); // 数据类型
const params = ref({
  tableName: "", // 表名
  delPrefix: "", // 删除前缀
  packageName: "cn.molu.system", // 所在包
  type: "java/entity.java", // 数据类型
});
const result = ref({});
const codeCache = new Map();
const tableName = ref("");
const preview = ref({
  activeName: "entity",
  data: [],
  title: "",
  open: false,
  code: null,
  vmName: "entity.java",
});
// 这里可以从后台获取,我这里为了直观的展示就写死了这几种
const tabPaneData = reactive<any[]>([
  {
    label: "entity.java",
    name: "entity",
    path: "java/entity.java",
  },
  {
    label: "controller.java",
    name: "controller",
    path: "java/controller.java",
  },
  {
    label: "service.java",
    name: "service",
    path: "java/service.java",
  },
  {
    label: "serviceImpl.java",
    name: "scopeImpl",
    path: "java/serviceImpl.java",
  },
  {
    label: "mapper.java",
    name: "mapper",
    path: "java/mapper.java",
  },
  {
    label: "mapperXml.xml",
    name: "mapperXml",
    path: "xml/mapper.xml",
  },
  {
    label: "params.java",
    name: "params",
    path: "java/params.java",
  },
  {
    label: "api.ts",
    name: "api",
    path: "vue3/api.ts",
  },
  {
    label: "page.vue",
    name: "page",
    path: "vue3/page.vue",
  },
  {
    label: "types.ts",
    name: "types",
    path: "vue3/types.ts",
  },
  {
    label: "sql.sql",
    name: "sql",
    path: "sql/sql.sql",
  },
]);

/**
 * 查询按钮点击事件
 */
const handleQuery = () => {
  getAllTables()
    .then(({ data }) => {
      columnDetailVoList.value = data;
    })
    .finally(() => {
      loading.value = false;
    });
};

/**
 * 重置按钮点击事件
 */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  handleQuery();
};
/** 关闭弹窗 */
const closeDialog = () => {
  dialog.visible = false;
};

/**
 * 显示弹框
 */
const openDialog = (row: ColumnDetailVo) => {
  dialog.visible = true;
  dialog.title = "代码预览";
  console.log(row);
  params.value.tableName = row.tableName || "";
  if (!params.value.delPrefix && row.tableName?.includes("_")) {
    params.value.delPrefix = row.tableName?.split("_")[0] + "_";
  }
  tableName.value = row.tableName || "";
  generateCode();
};

/**
 * 选择列表
 */
const changeSelect = (value: string) => {
  console.log(value);
  params.value.tableName = value;
  if (!params.value.delPrefix && value?.includes("_")) {
    params.value.delPrefix = value?.split("_")[0] + "_";
  }
  tableName.value = value || "";
  generateCode();
};

/**
 * 生成代码
 */
const generateCode = () => {
  let obj = codeCache.get(tableName.value) || {};
  let activeName = preview.value.activeName;
  if (obj && obj[activeName]) {
    let code = obj[activeName] || "";
    result.value = code;
    return;
  }
  getTableInfo(tableName.value, params.value).then((response) => {
    const data = response.data;
    preview.value.code = data;
    const vmName = preview.value.vmName || "";
    let language = vmName.substring(vmName.indexOf(".") + 1, vmName.length).toLowerCase();
    language =  ["vue", "html", "xml"].includes(language) ? "html" : language;
    language = language === 'js' ? "javascript": language;
    result.value = hljs.highlight(data || "", {
      language: language,
      ignoreIllegals: true,
    }).value;
    obj[activeName] = result.value || "";
    codeCache.set(tableName.value, obj);
    return result.value || "&nbsp;";
  });
};

/**
 * tab切换事件
 */
const tabChange = (tabPaneName: any) => {
  console.log(tabPaneName);
  preview.value.activeName = tabPaneName;
  const tabPane = tabPaneData.find((item) => item.name == tabPaneName);
  console.log(tabPane);
  preview.value.vmName = tabPane.label;
  params.value.type = tabPane.path;
  generateCode();
};
/**
 * 复制代码
 */
const clipboardSuccess = () => {
  ElMessage.success("复制成功");
};

/**
 * 选择页码,分页查询
 */
const updateCurrentPage = (page?: number) => {
  queryParams.page = page;
  handleQuery();
};

/**
 * 页面加载完成后执行
 */
onMounted(() => {
  handleQuery();
});
</script>

<template>
  <div class="app-container">
    <div class="search-container">
      <el-form ref="queryFormRef" :model="params" :inline="true">
        <!-- 表名 -->
        <el-form-item label="表名" prop="tableName">
          <el-input v-model="params.tableName" placeholder="表名" disabled />
        </el-form-item>
        <!-- 删除前缀 -->
        <el-form-item label="删除前缀" prop="delPrefix" v-if="false">
          <el-input
            v-model="params.delPrefix"
            placeholder="删除前缀"
            clearable
          />
        </el-form-item>
        <!-- 所在包 -->
        <el-form-item label="所在包" prop="packageName" v-if="false">
          <el-input
            v-model="params.packageName"
            placeholder="所在包"
            clearable
          />
        </el-form-item>
        <el-form-item>
          <el-button class="filter-item" type="primary" @click="handleQuery">
            <i-ep-search />
            搜索
          </el-button>
          <el-button @click="resetQuery"> <i-ep-refresh />重置 </el-button>
        </el-form-item>
      </el-form>
    </div>

    <el-card shadow="never" class="table-container">
      <el-scrollbar style="height: 400px">
        <el-table
          v-loading="loading"
          :data="columnDetailVoList"
          row-key="id"
          default-expand-all
          :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        >
          <!-- 复选款 -->
          <el-table-column type="selection" width="55" align="center" />
          <!-- 序号 -->
          <el-table-column
            type="index"
            width="55"
            align="center"
            label="序号"
          />
          <!-- 数据库名 -->
          <el-table-column
            label="数据库名"
            align="center"
            prop="tableSchema"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.tableSchema }}
            </template>
          </el-table-column>
          <!-- 表名 -->
          <el-table-column label="表名" align="center" prop="tableName">
            <template #default="scope">
              {{ scope.row.tableName }}
            </template>
          </el-table-column>
          <!-- 表注释 -->
          <el-table-column
            label="表注释"
            align="center"
            prop="tableComment"
            v-if="dataType == 0"
          >
            <template #default="scope">
              {{ scope.row.tableComment }}
            </template>
          </el-table-column>
          <!-- 表数据引擎 -->
          <el-table-column
            label="表引擎"
            align="center"
            prop="engine"
            v-if="dataType == 0"
          >
            <template #default="scope">
              {{ scope.row.engine }}
            </template>
          </el-table-column>
          <!-- 表字符集 -->
          <el-table-column
            label="字符集"
            align="center"
            prop="tableCollation"
            v-if="dataType == 0"
          >
            <template #default="scope">
              {{ scope.row.tableCollation }}
            </template>
          </el-table-column>
          <!-- 表中数据条数 -->
          <el-table-column
            label="数据量"
            align="center"
            prop="tableRows"
            v-if="dataType == 0"
          >
            <template #default="scope">
              {{ scope.row.tableRows }}
            </template>
          </el-table-column>
          <!-- 表创建时间 -->
          <el-table-column
            label="创建时间"
            align="center"
            prop="createTime"
            v-if="dataType == 0"
          >
            <template #default="scope">
              {{ scope.row.createTime }}
            </template>
          </el-table-column>

          <!-- 字段名 -->
          <el-table-column
            label="字段名"
            align="center"
            prop="columnName"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.columnName }}
            </template>
          </el-table-column>
          <!-- 默认值 -->
          <el-table-column
            label="默认值"
            align="center"
            prop="columnDefault"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.columnDefault }}
            </template>
          </el-table-column>
          <!-- 是否可为空:YES、NO -->
          <el-table-column
            label="可为空"
            align="center"
            prop="isNullable"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.isNullable }}
            </template>
          </el-table-column>
          <!-- 数据类型:int、varchar... -->
          <el-table-column
            label="数据类型"
            align="center"
            prop="dataType"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.dataType }}
            </template>
          </el-table-column>
          <!-- 字段类型:int、varchar(30)... -->
          <el-table-column
            label="字段类型"
            align="center"
            prop="columnType"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.columnType }}
            </template>
          </el-table-column>
          <!-- 是否主键:PRI -->
          <el-table-column
            label="是否主键"
            align="center"
            prop="columnKey"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.columnKey }}
            </template>
          </el-table-column>
          <!-- 是否自增:auto_increment(自增) -->
          <el-table-column
            label="是否自增"
            align="center"
            prop="extra"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.extra }}
            </template>
          </el-table-column>
          <!-- 字段注释 -->
          <el-table-column
            label="字段注释"
            align="center"
            prop="columnComment"
            v-if="dataType == 1"
          >
            <template #default="scope">
              {{ scope.row.columnComment }}
            </template>
          </el-table-column>
          <!-- 操作 -->
          <el-table-column
            label="操作"
            fixed="right"
            align="left"
            width="200"
            v-if="dataType == 0"
          >
            <template #default="scope">
              <el-button
                type="primary"
                link
                size="small"
                @click.stop="openDialog(scope.row)"
              >
                <i-ep-view />预览
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </el-scrollbar>
      <div
        style="
          margin: 15px auto;
          display: flex;
          align-items: center;
          justify-content: center;
        "
      >
        <!-- 这里是分页插件 -->
        <!-- <el-pagination
          background
          layout="prev, pager, next"
          :total="queryParams.total"
          :default-page-size="10"
          v-model:page-size="queryParams.size"
          v-model:current-page="queryParams.page"
          @update:current-page="updateCurrentPage"
        /> -->
      </div>
    </el-card>

    <!-- 预览弹窗 -->
    <el-dialog
      v-model="dialog.visible"
      :title="dialog.title"
      destroy-on-close
      append-to-body
      align-center
      lock-scroll
      width="80%"
      @close="closeDialog"
      top="3vh"
    >
      <el-form ref="queryFormRef" :model="params" :inline="true">
        <!-- 表名 -->
        <el-form-item label="表名" prop="tableName">
          <el-select
            v-model="params.tableName"
            placeholder="表名"
            @change="changeSelect"
          >
            <el-option
              v-for="(item, i) in columnDetailVoList"
              :key="i"
              :label="item.tableName"
              :value="item.tableName"
            />
          </el-select>
        </el-form-item>
        <!-- 删除前缀 -->
        <el-form-item label="删除前缀" prop="delPrefix">
          <el-input v-model="params.delPrefix" placeholder="删除前缀" />
        </el-form-item>
        <!-- 所在包 -->
        <el-form-item label="所在包" prop="packageName">
          <el-input v-model="params.packageName" placeholder="所在包" />
        </el-form-item>
        <el-form-item>
          <el-button class="filter-item" type="primary" @click="generateCode">
            生成
          </el-button>
        </el-form-item>
      </el-form>

      <el-tabs
        v-model="preview.activeName"
        class="demo-tabs"
        @tab-change="tabChange"
      >
        <el-scrollbar max-height="420px">
          <el-tab-pane
            v-for="(item, key) in tabPaneData"
            :label="item.label"
            :name="item.name"
            :key="key"
          >
            <!-- <el-link :underline="false" 
              icon="el-icon-document-copy" 
              v-clipboard:copy="preview.code" 
              v-clipboard:success="clipboardSuccess" 
              style="float:right">复制</el-link> -->
            
            <pre class="hljs-container">
              <code class="hljs" v-html="result"></code>
            </pre>
          </el-tab-pane>
        </el-scrollbar>
      </el-tabs>
    </el-dialog>
  </div>
</template>

<style lang="scss" scoped>
/* 语法高亮 */
.hljs-container {
  position: relative;
  display: block;
  // width: 600px;
  padding: 30px 5px 2px;
  overflow-x: hidden;
  line-height: 20px;
  text-align: left;
  background: #21252b;
  box-shadow: 0 10px 30px 0 rgb(0 0 0 / 40%);
}

/** 3个点 */
.hljs-container::before {
  position: absolute;
  top: 10px;
  left: 15px;
  width: 12px;
  height: 12px;
  overflow: visible;
  font-weight: 700;
  font-size: 16px;
  line-height: 12px;
  white-space: nowrap;
  text-indent: 75px;
  background-color: #fc625d;
  border-radius: 16px;
  box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
  content: attr(codetype);
}

/** 滚动条 */
:deep(.hljs) {
  overflow-x: auto;
  color: #4887b8;
  background-color: #21252b;
  // background-color: #dddddd;
}

:deep(.hljs::-webkit-scrollbar) {
  width: 12px !important;
  height: 12px !important;
}

:deep(.hljs::-webkit-scrollbar-thumb) {
  height: 30px !important;
  background: #d1d8e6;
  background-clip: content-box;
  border: 2px solid transparent;
  border-radius: 19px;
  opacity: 0.8;
}

:deep(.hljs::-webkit-scrollbar-thumb:hover) {
  background: #a5b3cf;
  background-clip: content-box;
  border: 2px solid transparent;
}

:deep(.hljs::-webkit-scrollbar-track-piece) {
  width: 30px;
  height: 30px;
  background: #333;
}

::-webkit-scrollbar-button {
  display: none;
}
</style>
1.6、代码生成器的使用
  • 选择需要生成代码的表
  • 根据个人需求是否去除表前缀
  • 生成的代码所在的包路径

点击生成即可生成代码,并可以直接复制使用

图片

图片
1.7、效果展示

1.7.1、菜单SQL

生成的菜单SQL语句,包含权限代码

图片

图片

1.7.2、生成的vue3页面

可以生成vue3的语法支持的前端页面

图片

图片

1.7.3、生成的xml文件

可以生成我们需要的mapper.xml文件

里面包含对数据库的增删改查的语句

图片

图片

1.7.4、生成的Service代码

这里可以生成对Service的代码实现类

图片

图片

1.7.5、生成的controller

生成对外提供的接口层,包含所需要的权限,接口问道的doc解释等

图片

图片

以上就是使用SpringBoot3、JDK17、Vue3、SpringSecurity6等实现的代码生成器了,其他页面就不一一展示了

优点:

  • 可扩展性高,可以根据自己不同的需求调整代码生成器以及生成逻辑
  • 可自定义生成内容,支持范围广
  • 可个自定义性化代码,按照自己喜欢的风格来自定义生成器