spring boot代码生成

Updated on in 程序人生 with 0 views and 0 comments

一、简介

Velocity是一个基于Java的模板引擎,它主要用于生成HTML页面或XML文档。Velocity将模板文件和数据模型结合起来,通过在模板中嵌入特定的语法和指令,生成最终的输出文档

二、依赖引入

pom文件添加依赖mybatis、spring-boot-starter-web、velocity依赖,mybatis配置参考低代码平台--spring boot集成mybatis和数据源管理 (wenyoulong.com),我们适用velocity模板引擎来生成java代码

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>${velocity.version}</version> <!-- 请检查是否有更新的版本 -->
        </dependency>

三、建立模板文件

在resources资源目录下新增模板

3.1 Entity.java.vm

package ${entity.basePackage}.${entity.entityPackage}.entity;

#foreach ($column in $entity.columns)
#if($column.javaTypePackage)
import $column.javaTypePackage;
#end
#end
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
 * ${entity.tableComment}
 */
@Data
@TableName("${entity.tableName}")
@ApiModel(value="${entity.className}对象", description="${entity.tableComment}")
public class ${entity.className} implements Serializable {
    private static final long serialVersionUID = 1L;
#foreach ($column in $entity.columns)

    /**
     * $column.dbColumnComment
     */
#if($column.isPrimaryKey)
    @TableId(type = IdType.ASSIGN_ID)
#end
#if($column.dbColumnType == 'date')
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern="yyyy-MM-dd")
#elseif($column.dbColumnType == 'datetime')
    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
#else
#end
    @ApiModelProperty(value = "${column.dbColumnComment}")
    private $column.javaType $column.javaAttrName;
#end
}

3.2 Controller.java.vm

package ${entity.basePackage}.${entity.entityPackage}.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import ${entity.basePackage}.${entity.entityPackage}.entity.${entity.className};
import ${entity.basePackage}.${entity.entityPackage}.service.I${entity.className}Service;
import cn.com.wenyl.bs.utils.R;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Slf4j
@RestController
@RequestMapping("/${entity.packagePath}/${entity.classAttrName}")
@Api(tags="${entity.tableComment}")
public class ${entity.className}Controller{

    @Resource(name = "${entity.classAttrName}Service")
    private I${entity.className}Service ${entity.classAttrName}Service;

    @PostMapping("/save")
    @ApiOperation(value="${entity.tableComment}-保存", notes="${entity.tableComment}-保存")
    public R<?> save(@RequestBody ${entity.className} entity){
        ${entity.classAttrName}Service.save(entity);
        return R.ok();
    }

    @DeleteMapping(value = "/removeById")
    @ApiOperation(value="${entity.tableComment}-根据ID删除", notes="${entity.tableComment}-根据ID删除")
    public R<?> removeById(@RequestParam(name="id") String id) {
        ${entity.classAttrName}Service.removeById(id);
        return R.ok("删除成功!");
    }

    @DeleteMapping(value = "/removeByIds")
    @ApiOperation(value="${entity.tableComment}-根据ID批量删除", notes="${entity.tableComment}-根据ID批量删除")
    public R<?> removeByIds(@RequestParam(name="ids") String ids) {
        ${entity.classAttrName}Service.removeByIds(Arrays.asList(ids.split(",")));
        return R.ok("批量删除成功!");
    }

    @RequestMapping(value = "/updateById", method = {RequestMethod.PUT,RequestMethod.POST})
    @ApiOperation(value="${entity.tableComment}-根据ID更新", notes="${entity.tableComment}-根据ID更新")
    public R<?> updateById(@RequestBody ${entity.className} entity){
        ${entity.classAttrName}Service.updateById(entity);
        return R.ok();
    }

    @GetMapping(value = "/queryById")
    @ApiOperation(value="${entity.tableComment}-通过id查询", notes="${entity.tableComment}-通过id查询")
    public R<${entity.className}> queryById(@RequestParam(name="id") String id) {
        ${entity.className} entity = ${entity.classAttrName}Service.getById(id);
        if(entity==null) {
            return R.error("未找到对应数据");
        }
        return R.ok(entity);
    }

    @GetMapping(value = "/list")
    @ApiOperation(value="${entity.tableComment}-分页查询", notes="${entity.tableComment}-分页列表查询")
    public R<IPage<${entity.className}>> queryPageList(@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
                                                       @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
                                                       HttpServletRequest req) {
        Page<${entity.className}> page = new Page<>(pageNo, pageSize);
        IPage<${entity.className}> pageList = ${entity.classAttrName}Service.page(page);
        return R.ok(pageList);
    }
}

3.3 IService.java.vm

package ${entity.basePackage}.${entity.entityPackage}.service;

import ${entity.basePackage}.${entity.entityPackage}.entity.${entity.className};
import com.baomidou.mybatisplus.extension.service.IService;

public interface I${entity.className}Service extends IService<${entity.className}> {

}

3.4 ServiceImpl.java.vm

package ${entity.basePackage}.${entity.entityPackage}.service.impl;

import ${entity.basePackage}.${entity.entityPackage}.entity.${entity.className};
import ${entity.basePackage}.${entity.entityPackage}.mapper.${entity.className}Mapper;
import ${entity.basePackage}.${entity.entityPackage}.service.I${entity.className}Service;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

@Service(value = "${entity.classAttrName}Service")
public class ${entity.className}ServiceImpl extends ServiceImpl<${entity.className}Mapper, ${entity.className}> implements I${entity.className}Service {

}

3.5 Mapper.java.vm

package ${entity.basePackage}.${entity.entityPackage}.mapper;

import ${entity.basePackage}.${entity.entityPackage}.entity.${entity.className};
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface ${entity.className}Mapper extends BaseMapper<${entity.className}> {

}

3.5 Mapper.xml.vm

<?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="${entity.basePackage}.${entity.entityPackage}.mapper.${entity.className}Mapper">

</mapper>

四、功能实现

建立好模板后,我们根据模板里的变量,来构造实体,然后通过我们传入的参数,去SQL找到对应的表,然后获取表的java类名,表字段的java属性名,属性类型,然后再生成代码

4.1 模板所需变量封装

4.1.1 表信息

package cn.com.wenyl.bs.generator.entity;

import lombok.Data;

import java.util.List;


/**
 * @author Swimming Dragon
 * @description: 表对应的java实体信息
 * @date 2023年12月04日 9:28
 */
@Data
public class TableEntity {
    // 基础包路径,默认为cn.com.wenyl.bs
    private String basePackage = "cn.com.wenyl.bs";
    // 代码包路径,与basePackage拼接作为完整代码包路径
    private String entityPackage;
    // 代码包路径,将.转化为/,用于controller路径
    private String packagePath;
    // 表名
    private String tableName;
    // 表注釋
    private String tableComment;
    // 实体名,首字母大写
    private String className;
    // 实体名,首字母小写
    private String classAttrName;
    // 列信息
    private List<TableColumnEntity> columns;

}

4.1.2 字段信息

package cn.com.wenyl.bs.generator.entity;

import lombok.Data;

/**
 * @author Swimming Dragon
 * @description: 表字段对应的实体属性信息
 * @date 2023年12月04日 9:28
 */
@Data
public class TableColumnEntity {
    // 字段对应的java类型
    private String javaType;
    // 字段对应的java包类路径
    private String javaTypePackage;
    // 字段对应的java属性名称
    private String javaAttrName;
    // 字段对应的数据库类型
    private String dbColumnType;
    // 字段名称
    private String dbColumnName;
    // 字段注释
    private String dbColumnComment;
    // 是否主键
    private Boolean isPrimaryKey;
}

4.2 常量信息

4.2.1 mysql与java类型映射

package cn.com.wenyl.bs.generator.constants;

import lombok.Getter;


/**
 * @author Swimming Dragon
 * @description: mysql常见字段类型对应的java字段类型
 * @date 2023年12月04日 9:28
 */
public enum MySQLFieldTypeEnum {
    VARCHAR("varchar","String","java.lang.String"),
    TEXT("varchar","String","java.lang.String"),
    INT("int","Integer","java.lang.Integer"),
    LONGTEXT("longtext","String","java.lang.String"),
    FLOAT("float","Float","java.lang.Float"),
    DOUBLE("double","Double","java.lang.Double"),
    BLOB("blob","byte[]",null),
    DATETIME("datetime","Date","java.util.Date"),
    DECIMAL("decimal","Double","java.lang.Double"),
    DATE("date","Date","java.util.Date");
    /*
    * sql字段类型
    * */
    final private String sqlType;
    /*
     * sql字段类型对应的java类型
     * */
    @Getter
    final private String javaType;
    /*
     * sql字段类型对应的java类型所在的包路径,用于import
     * */
    @Getter
    final private String javaTypeClassPath;

    MySQLFieldTypeEnum(String sqlType, String javaType, String javaTypeClassPath){
        this.sqlType = sqlType;
        this.javaType = javaType;
        this.javaTypeClassPath = javaTypeClassPath;
    }

    /**
     * 根据mysql的字段类型获取它对应的java实体字段类型和它所在的包路径
     * @param sqlType 字段对应的MySQL数据类型
     * @return 返回字段类型信息
     */
    public static MySQLFieldTypeEnum getMySQLFieldTypeEnumBySqlFieldType(String sqlType){
        MySQLFieldTypeEnum[] values = MySQLFieldTypeEnum.values();
        for(MySQLFieldTypeEnum mySQLFieldTypeEnum:values) {
            if (mySQLFieldTypeEnum.sqlType.equals(sqlType)) {
                return mySQLFieldTypeEnum;
            }
        }
        return null;
    }
}

4.2.2 模板所在文件目录

package cn.com.wenyl.bs.generator.constants;

/**
 * @author Swimming Dragon
 * @description: 模板文件所在路径
 * @date 2023年12月04日 16:44
 */
public class TemplateFilePath {
    public static final String ENTITY_TEMPLATE_PATH = "templates/code/Entity.java.vm";
    public static final String CONTROLLER_TEMPLATE_PATH = "templates/code/Controller.java.vm";

    public static final String SERVICE_TEMPLATE_PATH = "templates/code/IService.java.vm";
    public static final String SERVICE_IMPL_TEMPLATE_PATH = "templates/code/ServiceImpl.java.vm";

    public static final String MAPPER_TEMPLATE_PATH = "templates/code/Mapper.java.vm";
    public static final String MAPPER_XML_TEMPLATE_PATH = "templates/code/Mapper.xml.vm";
}

4.2.3 生成的文件名的前后缀

package cn.com.wenyl.bs.generator.constants;

/**
 * @author Swimming Dragon
 * @description: 定义生成代码的类名的开始结束符号,有需要可自行团价,用于{@link cn.com.wenyl.bs.generator.service.impl.CodeGeneratorServiceImpl}中的generatorCode方法设置文件名
 * @date 2023年12月04日 10:35
 */
public class FileName {
    public static final String ENTITY_END_WITH = ".java";
    public static final String CONTROLLER_END_WITH = "Controller.java";
    public static final String SERVICE_START_WITH = "I";
    public static final String SERVICE_END_WITH = "Service.java";
    public static final String SERVICE_IMPL_END_WITH = "ServiceImpl.java";
    public static final String MAPPER_END_WITH = "Mapper.java";
    public static final String MAPPER_XML_END_WITH = "Mapper.xml";
}

4.2.4 在基础包路径下对代码分包

package cn.com.wenyl.bs.generator.constants;

import cn.com.wenyl.bs.exceptions.GeneratorCodeException;

import java.io.File;

/**
 * @author Swimming Dragon
 * @description: 指定代码生成目录的结构,按照controller、service、entity、mapper对生成的代码分目录存储,不要随意改动这里的File.separator,如果要自定义目目录结构,需要结合{@link cn.com.wenyl.bs.generator.service.impl.CodeGeneratorServiceImpl}中的generatorCode方法的路径设置
 * @date 2023年12月04日 10:18
 */
public class FileDirectory {
    public static final String ENTITY_PATH = "entity"+ File.separator;
    public static final String CONTROLLER_PATH = "controller"+ File.separator;
    public static final String SERVICE_PATH = "service"+ File.separator;
    public static final String SERVICE_IMPL_PATH = "service"+File.separator+"impl"+ File.separator;
    public static final String MAPPER_PATH = "mapper"+ File.separator;
    public static final String MAPPER_XML_PATH = "mapper"+File.separator+"xml"+ File.separator;
}

4.3 模板引擎配置

package cn.com.wenyl.bs.generator.config;

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Swimming Dragon
 * @description: 配置VelocityEngine引擎
 * @date 2023年12月04日 9:28
 */
@Configuration
public class VelocityEngineConfig {
    @Bean
    public VelocityEngine init(){
        // 初始化 VelocityEngine 实例
        VelocityEngine engine = new VelocityEngine();
        // 从classpath加载模板
        engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        engine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        // 指定文件读取写入的编码
        engine.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8");
        engine.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8");
        engine.init();
        return engine;
    }
}

4.4 数据库查询接口

4.4.1 mapper接口

package cn.com.wenyl.bs.generator.mapper;


import cn.com.wenyl.bs.exceptions.GeneratorCodeException;
import cn.com.wenyl.bs.generator.entity.TableColumnEntity;
import cn.com.wenyl.bs.generator.entity.TableEntity;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author Swimming Dragon
 * @description: 查询数据库表和字段的元信息接口
 * @date 2023年12月04日 9:28
 */
public interface CodeGeneratorMapper{
    /**
     * 获取数据库表字段名、字段类型、字段注释、主键的isPrimary会被标识为true,其余为false {@link TableColumnEntity}
     * @param databaseName 数据库名称
     * @param tableName 表名称
     * @return 数据库表字段名、字段类型、字段注释信息 {@link TableEntity}
     */
    List<TableColumnEntity> getTableColumnInfo(@Param("databaseName") String databaseName,
                                                @Param("tableName") String tableName);
    /**
     * 获取数据库表名和表注释 {@link TableColumnEntity}
     * @param databaseName 数据库名称
     * @param tableName 表名称
     * @return 数据库表名和表注释 {@link TableEntity}
     */
    TableEntity getTableInfo(@Param("databaseName") String databaseName,
                             @Param("tableName") String tableName);
}

4.4.2 xml配置

<?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.com.wenyl.bs.generator.mapper.CodeGeneratorMapper">
    <select id="getTableInfo" resultType="cn.com.wenyl.bs.generator.entity.TableEntity">
        SELECT
            TABLE_NAME tableName, TABLE_COMMENT tableComment
        FROM
            INFORMATION_SCHEMA.TABLES
        WHERE
            TABLE_SCHEMA = #{databaseName} AND TABLE_NAME = #{tableName}
    </select>
    <select id="getTableColumnInfo" resultType="cn.com.wenyl.bs.generator.entity.TableColumnEntity">
        SELECT
            COLUMN_NAME dbColumnName,
            COLUMN_comment dbColumnComment,
            DATA_TYPE dbColumnType,
            CASE
                COLUMN_KEY
                WHEN 'PRI' THEN
                    TRUE ELSE FALSE
                END isPrimaryKey
        FROM
            INFORMATION_SCHEMA.COLUMNS
        WHERE
            TABLE_SCHEMA = #{databaseName}
            AND TABLE_NAME = #{tableName}
    </select>


</mapper>

4.5 服务接口

4.5.1 接口定义

package cn.com.wenyl.bs.generator.service;

import cn.com.wenyl.bs.generator.entity.TableColumnEntity;
import cn.com.wenyl.bs.generator.entity.TableEntity;
import cn.com.wenyl.bs.exceptions.GeneratorCodeException;

import java.io.IOException;
import java.util.List;

/**
 * 代码生成服务
 */
public interface ICodeGeneratorService {

    /**
     * 生成代码,包含entity、controller、service、mapper
     * 代码生成实际路径为filePath+basePackage+entityPackage  eg:D://+cn/com/wenyl/bs+/+mall
     * 其中D://为filePath,cn/com/wenyl/bs是basePackage将.转化成了/,entityPackage
     * @param filePath 代码生成得目录
     * @param basePackage 基础包路径
     * @param entityPackage 实体包路径
     * @param databaseName 数据库名称
     * @param tableName 表名称
     * @throws GeneratorCodeException 自定义代码生成异常 {@link GeneratorCodeException}
     * @throws IOException IO异常,捕获将代码写入到文件中发生异常的情况
     */
    void generatorCode(String filePath,String basePackage,String entityPackage,String databaseName,String tableName) throws GeneratorCodeException, IOException;

    /**
     * 获取数据库表对应的java代码的映射信息,包含包路径,实体类名,表名,列信息等信息,详情见 {@link TableEntity}
     * @param databaseName 数据库名称
     * @param tableName 表名称
     * @return 数据库表对应的java代码的映射信息 {@link TableEntity}
     * @throws GeneratorCodeException 自定义代码生成异常 {@link GeneratorCodeException}
     */
    TableEntity getTableEntity(String databaseName, String tableName) throws GeneratorCodeException;

    /**
     * 获取数据库表的列对应的java代码的映射信息 {@link TableColumnEntity}
     * @param databaseName 数据库名称
     * @param tableName 表名称
     * @return 数据库表的列对应的java代码的映射信息 {@link TableEntity}
     * @throws GeneratorCodeException 自定义代码生成异常 {@link GeneratorCodeException}
     */
    List<TableColumnEntity> getTableColumns(String databaseName, String tableName)  throws GeneratorCodeException;

    /**
     * 获取数据库列对应的java属性名,以_为标识,将列名分割为数组,遍历数组,除了第一个字符串外,其余字符串将首字母转化为大写,再拼接后返回
     * @param dbColumnName 列名
     * @return 将列名转换为对应的java属性名
     */
    String getJavaAttrName(String dbColumnName);

    /**
     * 获取数据库表对应的java实体名称,以_为标识,将列名分割为数组,遍历数组,将首字母都大写后拼接最后加上Entity后返回
     * @param dbTableName 数据库表名
     * @return 数据库表对应的java类名
     */
    String getJavaEntityName(String dbTableName);

}

4.5.2 接口实现

package cn.com.wenyl.bs.generator.service.impl;

import cn.com.wenyl.bs.generator.constants.FileDirectory;
import cn.com.wenyl.bs.generator.constants.FileName;
import cn.com.wenyl.bs.generator.constants.MySQLFieldTypeEnum;
import cn.com.wenyl.bs.generator.constants.TemplateFilePath;
import cn.com.wenyl.bs.generator.entity.TableColumnEntity;
import cn.com.wenyl.bs.generator.entity.TableEntity;
import cn.com.wenyl.bs.generator.mapper.CodeGeneratorMapper;
import cn.com.wenyl.bs.generator.service.ICodeGeneratorService;
import cn.com.wenyl.bs.exceptions.GeneratorCodeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.regex.Matcher;

@Service(value = "codeGeneratorService")
@Slf4j
public class CodeGeneratorServiceImpl implements ICodeGeneratorService {
    @Resource
    private CodeGeneratorMapper codeGeneratorMapper;
    @Resource
    private VelocityEngine engine;

    @Override
    public void generatorCode(String filePath,String basePackage, String entityPackage, String databaseName, String tableName) throws GeneratorCodeException,IOException {
        TableEntity tableEntity = getTableEntity(databaseName, tableName);
        tableEntity.setBasePackage(basePackage);
        tableEntity.setEntityPackage(entityPackage);
        // 将包路径转化为controller请求路径
        String packagePath = entityPackage.replaceAll("\\.","/");
        tableEntity.setPackagePath(packagePath);
        String baseDir = filePath + (basePackage+"."+entityPackage).replaceAll("\\.", Matcher.quoteReplacement(File.separator))+File.separator;
        String entityPath = baseDir + FileDirectory.ENTITY_PATH + tableEntity.getClassName()+ FileName.ENTITY_END_WITH;
        String controllerPath = baseDir + FileDirectory.CONTROLLER_PATH + tableEntity.getClassName()+FileName.CONTROLLER_END_WITH;
        String servicePath = baseDir + FileDirectory.SERVICE_PATH + FileName.SERVICE_START_WITH+tableEntity.getClassName()+FileName.SERVICE_END_WITH;
        String serviceImplPath = baseDir + FileDirectory.SERVICE_IMPL_PATH + tableEntity.getClassName()+FileName.SERVICE_IMPL_END_WITH;
        String MapperPath = baseDir + FileDirectory.MAPPER_PATH + tableEntity.getClassName()+FileName.MAPPER_END_WITH;
        String MapperXmlPath = baseDir + FileDirectory.MAPPER_XML_PATH + tableEntity.getClassName()+FileName.MAPPER_XML_END_WITH;
        VelocityContext velocityContext = createVelocityContext(tableEntity);
        generatorEntity(entityPath,velocityContext);
        generatorController(controllerPath,velocityContext);
        generatorService(servicePath,velocityContext);
        generatorServiceImpl(serviceImplPath,velocityContext);
        generatorMapper(MapperPath,velocityContext);
        generatorMapperXml(MapperXmlPath,velocityContext);
    }


    /**
     * 创建VelocityContext实例,并添加数据到上下文中
     * @param tableEntity 表信息
     * @return VelocityContext实例
     */
    private VelocityContext createVelocityContext(TableEntity tableEntity){
        VelocityContext context = new VelocityContext();
        context.put("entity",tableEntity);
        return context;
    }

    /**
     * 生成java实体的代码
     * @param filePath 代码生成的文件全路径
     * @param context 模板渲染所需的变量信息
     * @throws IOException 创建、写入文件时IO异常
     */
    private void generatorEntity(String filePath,VelocityContext context)  throws IOException {
        Template template = engine.getTemplate(TemplateFilePath.ENTITY_TEMPLATE_PATH);
        // 将模板和数据合并生成最终的 Java 代码字符串
        StringWriter writer = new StringWriter();
        template.merge(context, writer);

        writeCodeToFile(writer,filePath);
    }
    /**
     * 将代码写入到文件
     * @param writer 代码信息
     * @param filePath 代码生成的文件全路径
     * @throws IOException 创建、写入文件时IO异常
     */
    private void writeCodeToFile(StringWriter writer, String filePath) throws IOException {
        File file = new File(filePath);
        File directory = file.getParentFile();
        if(!directory.exists()){
            boolean createDirectorySuccess = directory.mkdirs();
            if(!createDirectorySuccess){
                throw new IOException("文件目录创建异常");
            }

        }
        if(file.exists()){
            boolean delete = file.delete();
            if(!delete){
                throw new IOException("代码文件已存在,删除代码文件异常");
            }
        }
        boolean createFileSuccess = file.createNewFile();
        if(createFileSuccess){
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(writer.toString());
            fileWriter.close();
        }else {
            throw new IOException("文件创建异常");
        }
    }

    /**
     * 生成controller的代码
     * @param filePath 代码生成的文件全路径
     * @param context 模板渲染所需的变量信息
     * @throws IOException 创建、写入文件时IO异常
     */
    private void generatorController(String filePath, VelocityContext context) throws IOException {
        Template template = engine.getTemplate(TemplateFilePath.CONTROLLER_TEMPLATE_PATH);
        // 将模板和数据合并生成最终的 Java 代码字符串
        StringWriter writer = new StringWriter();
        template.merge(context, writer);
        writeCodeToFile(writer,filePath);
    }

    /**
     * 生成service的代码
     * @param filePath 代码生成的文件全路径
     * @param context 模板渲染所需的变量信息
     * @throws IOException 创建、写入文件时IO异常
     */
    private void generatorService(String filePath, VelocityContext context) throws IOException {
        Template template = engine.getTemplate(TemplateFilePath.SERVICE_TEMPLATE_PATH);
        // 将模板和数据合并生成最终的 Java 代码字符串
        StringWriter writer = new StringWriter();
        template.merge(context, writer);

        writeCodeToFile(writer,filePath);
    }
    /**
     * 生成service实现类的代码
     * @param filePath 代码生成的文件全路径
     * @param context 模板渲染所需的变量信息
     * @throws IOException 创建、写入文件时IO异常
     */
    private void generatorServiceImpl(String filePath,VelocityContext context) throws IOException {
        Template template = engine.getTemplate(TemplateFilePath.SERVICE_IMPL_TEMPLATE_PATH);
        // 将模板和数据合并生成最终的 Java 代码字符串
        StringWriter writer = new StringWriter();
        template.merge(context, writer);

        writeCodeToFile(writer,filePath);
    }

    /**
     * 生成mapper的代码
     * @param filePath 代码生成的文件全路径
     * @param context 模板渲染所需的变量信息
     * @throws IOException 创建、写入文件时IO异常
     */
    private void generatorMapper(String filePath, VelocityContext context) throws IOException {
        Template template = engine.getTemplate(TemplateFilePath.MAPPER_TEMPLATE_PATH);
        // 将模板和数据合并生成最终的 Java 代码字符串
        StringWriter writer = new StringWriter();
        template.merge(context, writer);

        writeCodeToFile(writer,filePath);
    }
    /**
     * 生成mapper对应的xml的代码
     * @param filePath 代码生成的文件全路径
     * @param context 模板渲染所需的变量信息
     * @throws IOException 创建、写入文件时IO异常
     */
    private void generatorMapperXml(String filePath, VelocityContext context) throws IOException {
        Template template = engine.getTemplate(TemplateFilePath.MAPPER_XML_TEMPLATE_PATH);
        // 将模板和数据合并生成最终的 Java 代码字符串
        StringWriter writer = new StringWriter();
        template.merge(context, writer);

        writeCodeToFile(writer,filePath);
    }

    @Override
    public TableEntity getTableEntity(String databaseName, String tableName) throws GeneratorCodeException {
        TableEntity tableEntity = codeGeneratorMapper.getTableInfo(databaseName,tableName);
        if(tableEntity==null || StringUtils.isEmpty(tableEntity.getTableName())){
            throw new GeneratorCodeException("数据库"+databaseName+"或表"+tableName+"不存在");
        }
        // 获取实体类名,首字母小写
        String entityClass = getJavaEntityName(tableName);
        tableEntity.setClassName(entityClass);
        // 获取实体类对应的变量名,首字母小写
        String entityAttrName = getJavaAttrName(tableName);
        tableEntity.setClassAttrName(entityAttrName);
        // 获取表字段信息
        List<TableColumnEntity> tableColumns = getTableColumns(databaseName, tableName);
        tableEntity.setColumns(tableColumns);
        return tableEntity;
    }

    @Override
    public List<TableColumnEntity>  getTableColumns(String databaseName, String tableName) throws GeneratorCodeException {
        List<TableColumnEntity> tableColumnInfo = codeGeneratorMapper.getTableColumnInfo(databaseName, tableName);
        Set<String> javaTypeClass = new HashSet<>();
        for(TableColumnEntity entity:tableColumnInfo){
            // 获取mysql字段类型对应的java字段类型
            String dbColumnType = entity.getDbColumnType();
            MySQLFieldTypeEnum mySQLFieldTypeEnum = MySQLFieldTypeEnum.getMySQLFieldTypeEnumBySqlFieldType(dbColumnType);
            if(mySQLFieldTypeEnum == null){
                String message = "找不到"+dbColumnType+"对应的Java类型,"+"可以在cn.com.wenyl.bs.code.generator.constants.MySQLFieldTypeEnum中添加该类型";
                log.error(message);
                throw new GeneratorCodeException(message);
            }
            // 设置java字段类型
            String javaType = mySQLFieldTypeEnum.getJavaType();
            entity.setJavaType(javaType);
            // 设置java字段类型所在的包路径,这里用set控制包路径,防止在模板生成的时候重复引入
            String javaTypeClassPath = mySQLFieldTypeEnum.getJavaTypeClassPath();
            if(!javaTypeClass.contains(javaTypeClassPath)){
                javaTypeClass.add(javaTypeClassPath);
                entity.setJavaTypePackage(javaTypeClassPath);
            }
            String dbColumnName = entity.getDbColumnName();
            // 设置java属性名
            String javaAttrName = getJavaAttrName(dbColumnName);
            entity.setJavaAttrName(javaAttrName);

        }
        return tableColumnInfo;
    }


    @Override
    public String getJavaAttrName(String dbColumnName){
        // 为空直接返回null
        if(StringUtils.isEmpty(dbColumnName)){
            return null;
        }
        // 根据下划线分割为数组
        String[] nameArray = dbColumnName.split("_");
        // 长度唯一,直接返回
        if(nameArray.length == 1){
            return dbColumnName;
        }
        // 长度不唯一,从第二个字符串开始,将首字母变为大写
        StringBuilder ret = new StringBuilder();
        ret.append(nameArray[0]);
        for(int i=1;i<nameArray.length;i++){
            String currentChar = nameArray[i];
            String newStr = currentChar.substring(0,1).toUpperCase()+currentChar.substring(1);
            ret.append(newStr);
        }
        return ret.toString();
    }

    @Override
    public String getJavaEntityName(String dbTableName) {
        // 为空直接返回null
        if(StringUtils.isEmpty(dbTableName)){
            return null;
        }

        // 根据下划线分割为数组
        String[] nameArray = dbTableName.split("_");
        // 长度唯一,直接返回
        if(nameArray.length == 1){
            return dbTableName;
        }
        // 从第一个字符串开始,每个字符串首字母变为大写
        StringBuilder ret = new StringBuilder();
        for (String currentChar : nameArray) {
            String newStr = currentChar.substring(0, 1).toUpperCase() + currentChar.substring(1);
            ret.append(newStr);
        }
        return ret.toString();
    }
}


4.6 api接口

提供一个对外访问的restapi接口

package cn.com.wenyl.bs.generator.controller;

import cn.com.wenyl.bs.exceptions.GeneratorCodeException;
import cn.com.wenyl.bs.generator.service.ICodeGeneratorService;
import cn.com.wenyl.bs.utils.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;

/**
 * @author Swimming Dragon
 * @description: 代码生成接口
 * @date 2023年12月04日 16:10
 */
@RestController
@RequestMapping("/codeGenerator")
@Api(tags="代码生成")
public class CodeGeneratorController {
    @Resource(name = "codeGeneratorService")
    private ICodeGeneratorService codeGeneratorService;


    /**
     * 生成代码,包含entity、controller、service、mapper
     * 代码生成实际路径为filePath+basePackage+entityPackage  eg:D://+cn/com/wenyl/bs+/+mall
     * 其中D://为filePath,cn/com/wenyl/bs是basePackage将.转化成了/,entityPackage
     * @param filePath 代码生成得目录
     * @param basePackage 基础包路径
     * @param entityPackage 实体包路径
     * @param databaseName 数据库名称
     * @param tableName 表名称
     * @throws GeneratorCodeException 自定义代码生成异常 {@link GeneratorCodeException}
     * @throws IOException IO异常,捕获将代码写入到文件中发生异常的情况
     */
    @GetMapping("/save")
    @ApiOperation(value="订单-保存", notes="订单-保存")
    public R<?> generatorCode(@RequestParam("filePath") String filePath,@RequestParam("basePackage") String basePackage,
                              @RequestParam("entityPackage") String entityPackage, @RequestParam("databaseName") String databaseName,
                              @RequestParam("tableName") String tableName) throws GeneratorCodeException, IOException{
        codeGeneratorService.generatorCode(filePath,basePackage,entityPackage,databaseName,tableName);
        return R.ok("成功");
    }
}

五、测试

启动工程

在数据库中建一个表

CREATE TABLE `mall_order` (
  `id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT 'ID',
  `order_no` varchar(100) COLLATE utf8_bin NOT NULL COMMENT '订单编号',
  `wx_account_id` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '微信用户ID',
  `username` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '客户名称',
  `total_price` double(10,2) DEFAULT NULL COMMENT '订单总价',
  `order_state` int(10) DEFAULT '0' COMMENT '审核状态 0--未支付,1--已支付',
  `wx_user_address_id` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '收件信息ID',
  `wx_order_id` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '微信支付返回的订单编号',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  `del_flag` int(11) DEFAULT '0' COMMENT '删除状态,0未删除,1已删除',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `create_by` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '创建人',
  `update_by` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '更新人',
  `order_desc` longtext COLLATE utf8_bin COMMENT '订单备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC COMMENT='订单'

访问自己项目的swagger地址,我的是http://localhost:8082/bs/swagger-ui.html#,或者以postman来测试也行

image.png

从界面上传入一些参数进行测试

image.png

执行后,去设置的FilePath目录下就能看到生成的代码了

这里要注意,我们设置了一个根目录,就是filePath参数,然后设置了一个basePackage,和entityPackage,代码会将三个参数拼接,然后将.转化为/,构成最终代码生成的一个目录,在这个目录下,会按照设置好的代码分包,将代码分别放在controller、entity、service、mapper目录下

image.png


标题:spring boot代码生成
作者:wenyl
地址:http://www.wenyoulong.com/articles/2023/12/04/1701681375393.html