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资源目录下新增模板
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
}
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);
}
}
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}> {
}
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 {
}
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}> {
}
<?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属性名,属性类型,然后再生成代码
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;
}
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;
}
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;
}
}
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";
}
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";
}
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;
}
在
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;
}
}
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);
}
<?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>
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);
}
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();
}
}
提供一个对外访问的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来测试也行
从界面上传入一些参数进行测试
执行后,去设置的FilePath目录下就能看到生成的代码了
这里要注意,我们设置了一个根目录,就是filePath参数,然后设置了一个basePackage,和entityPackage,代码会将三个参数拼接,然后将.转化为/,构成最终代码生成的一个目录,在这个目录下,会按照设置好的代码分包,将代码分别放在controller、entity、service、mapper目录下