侧边栏壁纸
  • 累计撰写 75 篇文章
  • 累计创建 41 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

MyBatis Plus Generator 代码生成器 v3.5.x 案例,含校验、MapStruct、Swagger、QO、VO,自定义 FreeMarker 模板引擎

已删除用户
2021-07-16 / 0 评论 / 0 点赞 / 110 阅读 / 72373 字 / 正在检测是否收录...

v3.5.3、v3.5.3.1

和 v3.5.2(往下翻)区别不大,主要是FreemarkerTemplateEngine修改的多,但我还是全部重新贴一遍,我之前的写法中在 Controller 方法里进行了 try catch,但实际上可以用全局异常捕获。

参照阿里巴巴 Java 开发手册,将其中每一层传递参数的入参称为 Query Object,所以我命名为 XxxQO,目前是根据 entity 的属性生成的,生成后需要手动删除不需要的参数,以及更新参数校验注解。返回到页面的响应结果是 VO(View Object)。

另外我现在改用 springdoc-openapi-ui 了,所以有些注解还在用 Swagger 的需要改下。

代码不用全部复制,重点关注 CodeGenerator 和 FreemarkerTemplateEngine 这两个类就行,其他的模板文件可以从源码中拷贝出来再修改。

代码生成器

CodeGenerator.java

package top.zhogjianhao.mybatis;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import top.zhogjianhao.YamlUtils;
import top.zhogjianhao.base.BaseController;
import top.zhogjianhao.base.model.BaseEntity;
import top.zhogjianhao.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * 代码生成器
 */
@Component
public class CodeGenerator {

  private static final String DB_URL;
  private static final String DB_USERNAME;
  private static final String DB_PASSWORD;
  private static final String TABLE_PREFIX;
  private static final String OUTPUT_DIR = FileUtils.getProjectPath() + "/generator/src/main/java";

  static {
    Map<String, Object> yamlMap = YamlUtils.load(FileUtils.getProjectPath() + "/admin/src/main/resources/application.yml");

    Object urlObj = YamlUtils.get(yamlMap, "spring.datasource.url");
    DB_URL = urlObj != null ? urlObj.toString() : "";
    Object usernameObj = YamlUtils.get(yamlMap, "spring.datasource.username");
    DB_USERNAME = usernameObj != null ? usernameObj.toString() : "";
    Object passwordObj = YamlUtils.get(yamlMap, "spring.datasource.password");
    DB_PASSWORD = passwordObj != null ? passwordObj.toString() : "";
    Object prefixObj = YamlUtils.get(yamlMap, "mybatis-plus.global-config.db-config.table-prefix");
    TABLE_PREFIX = prefixObj != null ? prefixObj.toString() : "";
  }

  /**
   * 读取控制台输入内容
   */
  private static final Scanner SCANNER = new Scanner(System.in);

  /**
   * 控制台输入内容读取并打印提示信息
   *
   * @param message 提示信息
   * @return 输入内容
   */
  public static String scannerNext(String message) {
    System.out.println(message);
    String nextLine = SCANNER.nextLine();
    if (StringUtils.isBlank(nextLine)) {
      // 如果输入空行继续等待
      return SCANNER.next();
    }
    return nextLine;
  }

  protected static <T> T configBuilder(IConfigBuilder<T> configBuilder) {
    return null == configBuilder ? null : configBuilder.build();
  }

  public static void main(String[] args) throws IOException {
    // 自定义模板,key 为“自定义的包名:Entity 后缀”,value 为模板路径
    Map<String, String> customFile = new HashMap<>(8);
    customFile.put("model/qo:GetQO", "/generator/entityQO.java");
    customFile.put("model/qo:PageQO", "/generator/entityQO.java");
    customFile.put("model/qo:SaveQO", "/generator/entityQO.java");
    customFile.put("model/qo:UpdateQO", "/generator/entityQO.java");
    customFile.put("model/qo:RemoveQO", "/generator/entityQO.java");

    customFile.put("model/vo:PageVO", "/generator/entityVO.java");
    customFile.put("model/vo:GetVO", "/generator/entityVO.java");

    customFile.put("struct:Struct", "/generator/struct.java");

    // 删除之前生成的文件
    FileUtils.deleteDirectory(new File(OUTPUT_DIR));

    // 代码生成器
    new AutoGenerator(configBuilder(new DataSourceConfig.Builder(DB_URL, DB_USERNAME, DB_PASSWORD)))
      // 全局配置
      .global(configBuilder(new GlobalConfig.Builder()
        // 禁用打开生成目录
        .disableOpenDir()
        // 输出目录,默认 windows: D://  linux or mac: /tmp
        .outputDir(OUTPUT_DIR)
        // 作者,默认无
        .author("ZhongJianhao")
        // 注释时间(@since),默认 yyyy-MM-dd
        .commentDate("")
        // 开启 swagger 模式,默认 false
        .enableSwagger()
      ))
      // 包配置
      .packageInfo(configBuilder(new PackageConfig.Builder()
        // 模块名
        .moduleName("")
        // 实体包名
        .entity("model.entity")
        // 父包名
        .parent("top.zhogjianhao")
      ))
      // 自定义配置
      .injection(configBuilder(new InjectionConfig.Builder()
        .beforeOutputFile((tableInfo, stringObjectMap) -> {
          // 不启用 @TableName 注解
          // tableInfo.setConvert(false);

          // 自定义 Mapper XML 生成目录
          ConfigBuilder config = (ConfigBuilder) stringObjectMap.get("config");
          Map<OutputFile, String> pathInfoMap = config.getPathInfo();
          pathInfoMap.put(OutputFile.xml, pathInfoMap.get(OutputFile.xml).replaceAll("/java.*", "/resources/mapper"));
          stringObjectMap.put("config", config);
        })
        // 自定义文件,比如 VO
        .customFile(customFile)
      ))
      // 策略配置
      .strategy(configBuilder(new StrategyConfig.Builder()
        // 表名
        .addInclude(scannerNext("请输入表名(英文逗号分隔):").split(","))
        // 过滤表前缀
        .addTablePrefix(TABLE_PREFIX)

        // Entity 策略
        .entityBuilder()
        // 开启 Lombok 模式
        .enableLombok()
        // 禁用生成 serialVersionUID
        .disableSerialVersionUID()
        // 数据库表映射到实体的命名策略:下划线转驼峰
        .naming(NamingStrategy.underline_to_camel)
        // 主键策略为自增,默认 IdType.AUTO
        .idType(IdType.INPUT)
        // 父类
        .superClass(BaseEntity.class)
        // 覆盖已有文件
        .enableFileOverride()

        // Controller 策略
        .controllerBuilder()
        // 生成 @RestController 注解
        .enableRestStyle()
        // 父类
        .superClass(BaseController.class)
        .enableFileOverride()

        // Service 策略
        .serviceBuilder()
        .enableFileOverride()

        // Mapper 策略
        .mapperBuilder()
        .enableFileOverride()
      ))
      // 模板配置
      .template(configBuilder(new TemplateConfig.Builder()
        // 自定义模板:https://github.com/baomidou/generator/tree/develop/mybatis-plus-generator/src/main/resources/templates
        .entity("/generator/entity.java")
        .mapper("/generator/mapper.java")
        .service("/generator/service.java")
        .serviceImpl("/generator/serviceImpl.java")
        .controller("/generator/controller.java")
      ))

      // 执行并指定模板引擎
      .execute(new FreemarkerTemplateEngine());
  }
}

自定义模板引擎(FreeMarker)

FreemarkerTemplateEngine.java

package top.zhogjianhao.mybatis;

import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import top.zhogjianhao.StringUtils;

import java.io.File;
import java.util.List;
import java.util.Map;

/**
 * 自定义模板引擎处理,用于生成 QO、VO 等
 */
public class FreemarkerTemplateEngine extends com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine {

  @Override
  protected void outputCustomFile(List<CustomFile> customFiles, TableInfo tableInfo, Map<String, Object> objectMap) {
    String entityName = tableInfo.getEntityName();
    String parentPath = this.getPathInfo(OutputFile.parent);
    customFiles.forEach((file) -> {
      String filePath = StringUtils.isNotBlank(file.getFilePath()) ? file.getFilePath() : parentPath;
      if (StringUtils.isNotBlank(file.getPackageName())) {
        filePath = filePath + File.separator + file.getPackageName();
        filePath = filePath.replaceAll("\\.", "\\" + File.separator);
      }

      // 自定义的包名:Entity 后缀:model/qo:GetQO
      String[] customFileNames = file.getFileName().split(":");
      String fileName = filePath + (!customFileNames[0].startsWith("/") ? File.separator : "") + customFileNames[0] + File.separator + entityName + customFileNames[1] + ".java";
      String templatePath = file.getTemplatePath();
      if (!templatePath.endsWith(".ftl")) {
        templatePath += ".ftl";
      }

      // 在路由名后加 s
      String[] controllerMappingHyphens = objectMap.get("controllerMappingHyphen").toString().split("-");
      if (controllerMappingHyphens.length > 1) {
        StringBuilder controllerMappingHyphenStr = new StringBuilder(controllerMappingHyphens[0]);
        for (int i = 1; i < controllerMappingHyphens.length; i++) {
          controllerMappingHyphenStr.append(StringUtils.capitalize(controllerMappingHyphens[i]));
        }
        objectMap.put("controllerMappingHyphen", controllerMappingHyphenStr.append("s").toString());
      }
      // 新增文件名后缀(v3.5.1 还有的)
      objectMap.put("fileNameSuffix", customFileNames[1]);

      this.outputFile(new File(fileName), objectMap, templatePath, file.isFileOverride());
    });
  }
}

自定义模板 RESTful(FreeMarker)

src/main/resources/template/generator

controller.java.ftl

package ${package.Controller};

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import ${package.Parent}.base.model.PageVO;
import ${package.Parent}.base.model.R;
import ${package.Entity}.${entity};
import ${package.Entity?substring(0, package.Entity?last_index_of("."))}.qo.*;
import ${package.Entity?substring(0, package.Entity?last_index_of("."))}.vo.*;
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
import ${package.Service}.${table.serviceName};
import ${package.Parent}.struct.${entity}Struct;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

/**
 * ${table.comment!} 控制器
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
@Tag(name = "${table.comment!}")
@Slf4j
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>

  @Autowired
  private ${table.serviceName} ${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)};

  @Operation(summary = "${table.comment!}列表")
  @GetMapping
  public R<List<${entity}>> list(@Validated ${entity}PageQO query) {
    LambdaQueryWrapper<${entity}> queryWrapper = new LambdaQueryWrapper<${entity}>().orderByDesc(${entity}::getId);
    if (query.getCurrent() == 0) {
      return ok(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.list(queryWrapper));
    }
    return ok(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.page(query, queryWrapper));
  }

  @Operation(summary = "${table.comment!}详情")
  @GetMapping("/{id}")
  public R<${entity}GetVO> get(@Validated ${entity}GetQO query) {
    return ok(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.getById(query.getId()));
  }

  @Operation(summary = "保存${table.comment!}")
  @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
  @PostMapping
  public R save(@RequestBody @Validated ${entity}SaveQO obj) {
    return custom(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.save(${entity}Struct.INSTANCE.to(obj)), SAVE_FAILED_MSG);
  }

  @Operation(summary = "更新${table.comment!}")
  @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
  @PutMapping("/{id}")
  public R update(@RequestBody @Validated ${entity}UpdateQO obj) {
    return custom(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.updateById(${entity}Struct.INSTANCE.to(obj)), UPDATE_FAILED_MSG);
  }

  @Operation(summary = "删除${table.comment!}")
  @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
  @DeleteMapping("/{ids}")
  public R remove(@Validated ${entity}RemoveQO query) {
    return custom(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.removeByIds(Arrays.asList(query.getIds().split(","))), REMOVE_FAILED_MSG);
  }
}
</#if>

service.java.ftl

package ${package.Service};

import ${package.Entity}.${entity};
import ${superServiceClassPackage};

/**
 * ${table.comment!} 服务
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {

}
</#if>

serviceImpl.java.ftl

package ${package.ServiceImpl};

import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * ${table.comment!} 服务实现
 *
<#if author != "">
  * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
@Service
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {

}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {

  @Autowired
  private ${table.mapperName} ${table.mapperName?substring(0, 1)?lower_case}${table.mapperName?substring(1)};

}
</#if>

mapper.java.ftl

package ${package.Mapper};

import ${package.Entity}.${entity};
import ${superMapperClassPackage};

/**
 * ${table.comment!} Mapper
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if kotlin>
interface ${table.mapperName} : ${superMapperClass}<${entity}>
<#else>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {

}
</#if>

entity.java.ftl

package ${package.Entity};

<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger??>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
  <#if chainModel>
import lombok.experimental.Accessors;
  </#if>
</#if>

/**
 * ${table.comment!}
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if swagger??>
@Schema(title = "${table.comment!}")
</#if>
<#if table.convert>
@TableName("${table.name}")
</#if>
<#if entityLombokModel>
  <#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
  <#else>
@EqualsAndHashCode(callSuper = false)
  </#if>
  <#if chainModel>
@Accessors(chain = true)
  </#if>
@Data
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#else>
public class ${entity} <#if entitySerialVersionUID>implements Serializable</#if> {
</#if>

<#if entitySerialVersionUID>
  private static final long serialVersionUID = 1L;

</#if>
<#-- ----------  BEGIN 字段循环遍历  -------- -->
<#list table.fields as field>
  <#-- 排除公共字段 -->
  <#if field.propertyName != 'id' && field.propertyName != 'tenantId' && field.propertyName != 'deleted' && field.propertyName != 'createdTime' && field.propertyName != 'createdBy' && field.propertyName != 'updatedTime' && field.propertyName != 'updatedBy' && field.propertyName != 'remark'>
  <#if field.keyFlag>
    <#assign keyPropertyName="${field.propertyName}"/>
  </#if>
  /**
   * ${field.comment}
   */
  <#if field.comment!?length gt 0 && swagger??>
  @Schema(title = "${field.comment}")
  </#if>
  <#if field.keyFlag>
    <#-- 主键 -->
    <#if field.keyIdentityFlag>
  @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
    <#elseif idType??>
  @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
    <#elseif field.convert>
  @TableId("${field.annotationColumnName}")
    </#if>
    <#-- 普通字段 -->
  <#elseif field.fill??>
  <#-- -----   存在字段填充设置   ----->
    <#if field.convert>
  @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
    <#else>
  @TableField(fill = FieldFill.${field.fill})
    </#if>
  <#elseif field.convert>
  @TableField("${field.annotationColumnName}")
  </#if>
  <#-- 乐观锁注解 -->
  <#if field.versionField>
  @Version
  </#if>
  <#-- 逻辑删除注解 -->
  <#if field.logicDeleteField>
  @TableLogic
  </#if>
  private ${field.propertyType} ${field.propertyName};
  </#if>
</#list>
<#-- ----------  END 字段循环遍历  -------- -->
<#-- Lombok 模式 -->
<#if !entityLombokModel>
  <#list table.fields as field>
    <#if field.propertyType == "boolean">
      <#assign getprefix="is"/>
    <#else>
      <#assign getprefix="get"/>
    </#if>
  public ${field.propertyType} ${getprefix}${field.capitalName}() {
    return ${field.propertyName};
  }

  <#if chainModel>
  public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  <#else>
  public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  </#if>
    this.${field.propertyName} = ${field.propertyName};
    <#if chainModel>
    return this;
    </#if>
  }
  </#list>
</#if>
<#-- 列常量 -->
<#if entityColumnConstant>
  <#list table.fields as field>
  public static final String ${field.name?upper_case} = "${field.name}";

  </#list>
</#if>
<#if activeRecord>
  @Override
  protected Serializable pkVal() {
  <#if keyPropertyName??>
    return this.${keyPropertyName};
  <#else>
    return null;
  </#if>
  }

</#if>
<#if !entityLombokModel>
  @Override
  public String toString() {
    return "${entity}{" +
  <#list table.fields as field>
    <#if field_index==0>
      "${field.propertyName}=" + ${field.propertyName} +
    <#else>
      ", ${field.propertyName}=" + ${field.propertyName} +
    </#if>
  </#list>
    "}";
  }
</#if>
}

entityQO.java.ftl

package ${package.Entity}.qo;

<#if fileNameSuffix?contains('PageQO')>
import top.zhogjianhao.base.model.BasePageQO;
<#elseif fileNameSuffix?contains('GetQO')>
import top.zhogjianhao.base.model.BaseGetQO;
</#if>
import ${package.Entity}.${entity};
<#--<#list table.importPackages as pkg>
import ${pkg};
</#list>-->
<#if swagger??>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
  <#if chainModel>
import lombok.experimental.Accessors;
  </#if>
</#if>

import javax.validation.constraints.*;
import java.time.LocalDateTime;

/**
 * ${table.comment!}<#if fileNameSuffix?contains('Page')>分页<#elseif fileNameSuffix?contains('Get')>详情<#elseif fileNameSuffix?contains('Save')>保存<#elseif fileNameSuffix?contains('Update')>更新<#elseif fileNameSuffix?contains('Remove')>删除<#else></#if>入参
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if swagger??>
@Schema(title = "${table.comment!} <#if fileNameSuffix?contains('Page')>分页入参<#elseif fileNameSuffix?contains('Get')>详情入参<#elseif fileNameSuffix?contains('Save')>保存入参<#elseif fileNameSuffix?contains('Update')>更新入参<#elseif fileNameSuffix?contains('Remove')>删除入参<#else></#if>")
</#if>
<#if entityLombokModel>
  <#if fileNameSuffix?contains('PageQO')>
@EqualsAndHashCode(callSuper = true)
  <#else>
@EqualsAndHashCode(callSuper = false)
  </#if>
  <#if chainModel>
@Accessors(chain = true)
  </#if>
@Data
</#if>
<#if fileNameSuffix?contains("PageQO")>
public class ${entity}${fileNameSuffix} extends BasePageQO<${entity}> {
<#elseif fileNameSuffix?contains("GetQO")>
public class ${entity}${fileNameSuffix} extends BaseGetQO {
<#else>
public class ${entity}${fileNameSuffix} <#if entitySerialVersionUID>implements Serializable</#if> {
</#if>

<#if entitySerialVersionUID>
  private static final long serialVersionUID = 1L;

</#if>
<#-- 如果包含是删除,添加多个 ID 的字段 -->
<#if fileNameSuffix?contains('Remove')>
  @Schema(title = "多个${table.comment!} ID,逗号分隔")
  private String ids;
</#if>
<#-- 临时写法 -->
<#if fileNameSuffix?contains('Get') || fileNameSuffix?contains('Update')>
  @Schema(title = "ID")
  @Min(value = 1, message = "${table.comment!} ID 错误")
  private Long id;
</#if>
<#-- ----------  BEGIN 字段循环遍历  -------- -->
<#list table.fields as field>
  <#-- 排除公共字段 -->
  <#if (fileNameSuffix?contains('Get') && field.propertyName=='id') || (fileNameSuffix?contains('Update') && field.propertyName!='createdTime') || (field.propertyName!='tenantId'&&field.propertyName!='createdBy'&&field.propertyName!='updatedBy'&&field.propertyName!='updatedTime')>
  <#if field.keyFlag>
    <#assign keyPropertyName="${field.propertyName}"/>
  </#if>
  <#if field.comment!?length gt 0 && swagger??>
  @Schema(title = "${field.comment}")
  </#if>
  <#-- 校验 -->
  <#if field.propertyType == 'Long'>
  @NotNull(message = "${field.comment}不能为空")
  @Min(value = 1, message = "${field.comment} 错误")
  <#elseif field.propertyType == 'String'>
  @NotBlank(message = "${field.comment}不能为空")
  <#elseif field.propertyType == 'LocalDateTime'>
  @NotNull(message = "${field.comment}不能为空")
  <#else>
  </#if>
  <#if field.keyFlag>
    <#-- 主键 -->
    <#if field.keyIdentityFlag>
  @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
    <#elseif idType??>
  @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
    <#elseif field.convert>
  @TableId("${field.annotationColumnName}")
    </#if>
    <#-- 普通字段 -->
  <#elseif field.fill??>
  <#-- -----   存在字段填充设置   ----->
    <#if field.convert>
  @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
    <#else>
  @TableField(fill = FieldFill.${field.fill})
    </#if>
  <#elseif field.convert>
  @TableField("${field.annotationColumnName}")
  </#if>
  <#-- 乐观锁注解 -->
  <#if field.versionField>
  @Version
  </#if>
  <#-- 逻辑删除注解 -->
  <#if field.logicDeleteField>
  @TableLogic
  </#if>
  private ${field.propertyType} ${field.propertyName};
  </#if>
</#list>
<#-- ----------  END 字段循环遍历  -------- -->
<#-- Lombok 模式 -->
<#if !entityLombokModel>
  <#list table.fields as field>
    <#if field.propertyType == "boolean">
      <#assign getprefix="is"/>
    <#else>
      <#assign getprefix="get"/>
    </#if>
  public ${field.propertyType} ${getprefix}${field.capitalName}() {
    return ${field.propertyName};
  }

  <#if chainModel>
  public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  <#else>
  public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  </#if>
    this.${field.propertyName} = ${field.propertyName};
    <#if chainModel>
    return this;
    </#if>
  }
  </#list>
</#if>
<#-- 列常量 -->
<#if entityColumnConstant>
  <#list table.fields as field>
  public static final String ${field.name?upper_case} = "${field.name}";

  </#list>
</#if>
<#if activeRecord>
  @Override
  protected Serializable pkVal() {
  <#if keyPropertyName??>
    return this.${keyPropertyName};
  <#else>
    return null;
  </#if>
  }

</#if>
<#if !entityLombokModel>
  @Override
  public String toString() {
    return "${entity}{" +
  <#list table.fields as field>
    <#if field_index==0>
      "${field.propertyName}=" + ${field.propertyName} +
    <#else>
      ", ${field.propertyName}=" + ${field.propertyName} +
    </#if>
  </#list>
    "}";
  }
</#if>
}

entityVO.java.ftl

package ${package.Entity}.vo;

<#if fileNameSuffix?contains('PageVO')>
import top.zhogjianhao.base.model.PageVO;
<#elseif fileNameSuffix?contains('GetVO')>
import top.zhogjianhao.base.model.BaseEntity;
</#if>
<#--<#list table.importPackages as pkg>
import ${pkg};
</#list>-->
<#if swagger??>
import io.swagger.v3.oas.annotations.media.Schema;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
  <#if chainModel>
import lombok.experimental.Accessors;
  </#if>
</#if>

/**
 * ${table.comment!}<#if fileNameSuffix?contains('List')>分页<#elseif fileNameSuffix?contains('Get')>详情<#else></#if>响应
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if swagger??>
@Schema(title = "${table.comment!} <#if fileNameSuffix?contains('Page')>分页响应<#elseif fileNameSuffix?contains('Get')>详情响应<#else></#if>")
</#if>
<#if entityLombokModel>
  <#if fileNameSuffix?contains('PageVO') || fileNameSuffix?contains('GetVO')>
@EqualsAndHashCode(callSuper = true)
  <#else>
@EqualsAndHashCode(callSuper = false)
  </#if>
  <#if chainModel>
@Accessors(chain = true)
  </#if>
@Data
</#if>
<#if fileNameSuffix?contains("PageVO")>
public class ${entity + fileNameSuffix} extends PageVO {
<#elseif fileNameSuffix?contains('GetVO')>
public class ${entity + fileNameSuffix} extends BaseEntity {
<#else>
public class ${entity + fileNameSuffix} <#if entitySerialVersionUID>implements Serializable</#if> {
</#if>

<#if entitySerialVersionUID>
  private static final long serialVersionUID = 1L;

</#if>
<#-- ----------  BEGIN 字段循环遍历  -------- -->
<#list table.fields as field>
  <#-- 排除公共字段 -->
  <#if field.propertyName != 'id' && field.propertyName != 'tenantId' && field.propertyName != 'deleted' && field.propertyName != 'createdTime' && field.propertyName != 'createdBy' && field.propertyName != 'updatedTime' && field.propertyName != 'updatedBy' && field.propertyName != 'remark'>
  <#if field.keyFlag>
    <#assign keyPropertyName="${field.propertyName}"/>
  </#if>
  <#if field.comment!?length gt 0 && swagger??>
  @Schema(title = "${field.comment}")
  </#if>
  <#if field.keyFlag>
    <#-- 主键 -->
    <#if field.keyIdentityFlag>
  @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
    <#elseif idType??>
  @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
    <#elseif field.convert>
  @TableId("${field.annotationColumnName}")
    </#if>
    <#-- 普通字段 -->
  <#elseif field.fill??>
  <#-- -----   存在字段填充设置   ----->
    <#if field.convert>
  @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
    <#else>
  @TableField(fill = FieldFill.${field.fill})
    </#if>
  <#elseif field.convert>
  @TableField("${field.annotationColumnName}")
  </#if>
  <#-- 乐观锁注解 -->
  <#if field.versionField>
  @Version
  </#if>
  <#-- 逻辑删除注解 -->
  <#if field.logicDeleteField>
  @TableLogic
  </#if>
  private ${field.propertyType} ${field.propertyName};
  </#if>
</#list>
<#-- ----------  END 字段循环遍历  -------- -->
<#-- Lombok 模式 -->
<#if !entityLombokModel>
  <#list table.fields as field>
    <#if field.propertyType == "boolean">
      <#assign getprefix="is"/>
    <#else>
      <#assign getprefix="get"/>
    </#if>
  public ${field.propertyType} ${getprefix}${field.capitalName}() {
    return ${field.propertyName};
  }

  <#if chainModel>
  public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  <#else>
  public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  </#if>
    this.${field.propertyName} = ${field.propertyName};
    <#if chainModel>
    return this;
    </#if>
  }
  </#list>
</#if>
<#-- 列常量 -->
<#if entityColumnConstant>
  <#list table.fields as field>
  public static final String ${field.name?upper_case} = "${field.name}";

  </#list>
</#if>
<#if activeRecord>
  @Override
  protected Serializable pkVal() {
  <#if keyPropertyName??>
    return this.${keyPropertyName};
  <#else>
    return null;
  </#if>
  }

</#if>
<#if !entityLombokModel>
  @Override
  public String toString() {
    return "${entity}{" +
  <#list table.fields as field>
    <#if field_index==0>
      "${field.propertyName}=" + ${field.propertyName} +
    <#else>
      ", ${field.propertyName}=" + ${field.propertyName} +
    </#if>
  </#list>
    "}";
  }
</#if>
}

struct.java.ftl

package ${package.Parent};

import ${package.Entity}.${entity};
import ${package.Entity?substring(0, package.Entity?last_index_of("."))}.qo.*;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface ${entity}Struct {

  ${entity}Struct INSTANCE = Mappers.getMapper(${entity}Struct.class);

  ${entity} to(${entity}SaveQO obj);

  ${entity} to(${entity}UpdateQO obj);
}

生成后的文件用到的自建类

分页响应

PageVO.java

package top.zhogjianhao.base.model;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Schema(title = "分页响应")
@Data
public class PageVO<T> implements Serializable {

  @Schema(title = "分页数据")
  private List<T> records;
  // @Schema(title = "当前页码")
  // private long current;
  // @Schema(title = "每页条数")
  // private long size;
  // @Schema(title = "总条数")
  // private long total;

  public PageVO() {
  }

  public PageVO(Page<T> page) {
    this.records = page.getRecords();
    // this.total = page.getTotal();
    // this.size = page.getSize();
    // this.current = page.getCurrent();
  }
}

响应结果

R.java

package top.zhogjianhao.base.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Schema(title = "响应结果")
@Data
public class R<T> {

  @Schema(title = "编码")
  private int code;
  @Schema(title = "消息")
  private String msg;
  @Schema(title = "数据")
  private T data;

  @Schema(title = "当前页码")
  private Long current;
  @Schema(title = "每页条数")
  private Long size;
  @Schema(title = "总条数")
  private Long total;

  public R() {
  }

  public R(int code) {
    this.code = code;
  }

  public R(int code, String msg) {
    this.code = code;
    this.msg = msg;
  }

  public R(int code, String msg, T data) {
    this.code = code;
    this.msg = msg;
    this.data = data;
  }
}

分页入参

BasePageQO

package top.zhogjianhao.base.model;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.media.Schema;

import java.io.Serializable;
import java.util.List;

/**
 * 分页入参,用于隐藏非分页参数
 *
 * @param <T> 实体类型
 * @author ZhongJianhao
 */
public class BasePageQO<T> extends Page<T> implements Serializable {

  @Schema(hidden = true)
  protected List<T> records;
  @Schema(hidden = true)
  protected long total;
  @Schema(title = "每页条数")
  protected long size;
  @Schema(title = "当前页码")
  protected Long current;
  @Schema(hidden = true)
  protected List<OrderItem> orders;
  @Schema(hidden = true)
  protected boolean optimizeCountSql;
  @Schema(hidden = true)
  protected boolean searchCount;
  @Schema(hidden = true)
  protected boolean optimizeJoinOfCountSql;
  @Schema(hidden = true)
  protected String countId;
  @Schema(hidden = true)
  protected Long maxLimit;
  @Schema(hidden = true)
  protected Long pages;

  @Override
  public long getCurrent() {
    // 不传 current 参数时,默认为 0,为 0 时忽略分页相关出参
    if (current == null) {
      return 0;
    }
    return current;
  }

  @Override
  public Page<T> setCurrent(long current) {
    this.current = current;
    return this;
  }

  @Override
  public long getSize() {
    return size;
  }

  @Override
  public Page<T> setSize(long size) {
    this.size = size;
    return this;
  }
}

详情入参

BaseGetQO.java

package top.zhogjianhao.base.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import javax.validation.constraints.Min;
import java.io.Serializable;

@Data
public class BaseGetQO implements Serializable {

  @Schema(title = "ID")
  @Min(value = 1, message = "ID 错误")
  private Long id;
}

v3.5.1

代码生成器

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.irms.config.mybatis.MyFreemarkerTemplateEngine;
import com.irms.controller.BaseController;
import com.irms.domain.entity.BaseEntity;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.function.BiConsumer;

/**
 * 代码生成器:https://github.com/baomidou/generator/blob/develop/mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/SimpleAutoGenerator.java
 *
 * @author duanluan
 */
public class CodeGenerator {

  /**
   * 读取控制台输入内容
   */
  private static final Scanner SCANNER = new Scanner(System.in);

  /**
   * 控制台输入内容读取并打印提示信息
   *
   * @param message 提示信息
   * @return
   */
  public static String scannerNext(String message) {
    System.out.println(message);
    String nextLine = SCANNER.nextLine();
    if (StringUtils.isBlank(nextLine)) {
      // 如果输入空行继续等待
      return SCANNER.next();
    }
    return nextLine;
  }

  protected static <T> T configBuilder(IConfigBuilder<T> configBuilder) {
    return null == configBuilder ? null : configBuilder.build();
  }

  public static void main(String[] args) {
    // 自定义模板,key 为 自定义的包名:Entity 后缀,value 为模板路径
    Map<String, String> customFile = new HashMap<>();
    customFile.put("domain/qo:GetQO", "/template/generator/entityQO.java");
    customFile.put("domain/qo:ListQO", "/template/generator/entityQO.java");
    customFile.put("domain/qo:SaveQO", "/template/generator/entityQO.java");
    customFile.put("domain/qo:UpdateQO", "/template/generator/entityQO.java");
    customFile.put("domain/qo:RemoveQO", "/template/generator/entityQO.java");

    customFile.put("domain/vo:ListVO", "/template/generator/entityVO.java");
    customFile.put("domain/vo:GetVO", "/template/generator/entityVO.java");

    customFile.put("struct:Struct", "/template/generator/struct.java");

    // 代码生成器
    new AutoGenerator(configBuilder(new DataSourceConfig.Builder("jdbc:mysql://localhost:3306/xxx?characterEncoding=UTF-8&useSSL=false&useUnicode=true&serverTimezone=UTC", "root", "")))
      // 全局配置
      .global(configBuilder(new GlobalConfig.Builder()
        // 覆盖已生成文件,默认 false
        .fileOverride()
        // 禁用打开生成目录
        // .disableOpenDir()
        // 输出目录,默认 windows: D://  linux or mac: /tmp
        .outputDir("/generator/src/main/java")
        // .outputDir(System.getProperty("user.dir") + "/generator/src/main/java")
        // 作者,默认无
        // .author("")
        // 注释时间(@since),默认 yyyy-MM-dd
        .commentDate("")
        // 开启 swagger 模式,默认 false
        .enableSwagger()
      ))
      // 包配置
      .packageInfo(configBuilder(new PackageConfig.Builder()
        // 模块名
        .moduleName("")
        // 实体包名
        .entity("domain")
        // 父包名
        .parent("top.zhogjianhao")
      ))
      // 自定义配置
      .injection(configBuilder(new InjectionConfig.Builder()
        .beforeOutputFile(new BiConsumer<TableInfo, Map<String, Object>>() {
          @Override
          public void accept(TableInfo tableInfo, Map<String, Object> stringObjectMap) {
            // 不启用 @TableName 注解
            // tableInfo.setConvert(false);

            // 自定义 Mapper XML 生成目录
            ConfigBuilder config = (ConfigBuilder) stringObjectMap.get("config");
            Map<OutputFile, String> pathInfoMap = config.getPathInfo();
            pathInfoMap.put(OutputFile.mapperXml, pathInfoMap.get(OutputFile.mapperXml).replaceAll("/java.*", "/resources/mapper"));
            stringObjectMap.put("config", config);
          }
        })
        // 自定义文件,比如 VO
        .customFile(customFile)
      ))
      // 策略配置
      .strategy(configBuilder(new StrategyConfig.Builder()
        // 表名
        .addInclude(scannerNext("请输入表名(英文逗号分隔):").split(","))
        // 过滤表前缀
        .addTablePrefix("t_")

        // Entity 策略配置
        .entityBuilder()
        // 开启 Lombok 模式
        .enableLombok()
        // 禁用生成 serialVersionUID
        .disableSerialVersionUID()
        // 数据库表映射到实体的命名策略:下划线转驼峰
        .naming(NamingStrategy.underline_to_camel)
        // 主键策略为自增,默认 IdType.AUTO
        .idType(IdType.AUTO)
        // 父类
        .superClass(BaseEntity.class)

        // Controller 策略配置
        .controllerBuilder()
        // 生成 @RestController 注解
        .enableRestStyle()
        // 父类
        .superClass(BaseController.class)
      ))
      // 模板配置
      .template(configBuilder(new TemplateConfig.Builder()
        // 自定义模板:https://github.com/baomidou/generator/tree/develop/mybatis-plus-generator/src/main/resources/templates
        .entity("/template/generator/entity.java")
        .mapper("/template/generator/mapper.java")
        .service("/template/generator/service.java")
        .serviceImpl("/template/generator/serviceImpl.java")
        .controller("/template/generator/controller.java")
      ))

      // 执行并指定模板引擎
      .execute(new MyFreemarkerTemplateEngine());
    // .execute(new FreemarkerTemplateEngine());
  }
}


自定义模板引擎(FreeMarker)

import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.util.Map;

/**
 * 自定义模板引擎处理,用于生成 QO、VO 等
 */
public class MyFreemarkerTemplateEngine extends FreemarkerTemplateEngine {

  @Override
  protected void outputCustomFile(Map<String, String> customFile, TableInfo tableInfo, Map<String, Object> objectMap) {
    String entityName = tableInfo.getEntityName();
    String otherPath = this.getPathInfo(OutputFile.other);
    customFile.forEach((key, value) -> {
      // 根据自定义路径替换 other 路径
      String[] keys = key.split(":");
      if (keys.length > 1) {
        key = keys[1];
      }
      String fileName = String.format((keys.length > 1 ? otherPath.replace("\\other", "\\" + keys[0]) : otherPath) + File.separator + entityName + "%s.java", key);
      objectMap.put("fileNameSuffix", key);

      // 处理路由名,结尾加 s
      String[] controllerMappingHyphens = objectMap.get("controllerMappingHyphen").toString().split("-");
      if (controllerMappingHyphens.length > 1) {
        StringBuilder controllerMappingHyphenStr = new StringBuilder(controllerMappingHyphens[0]);
        for (int i = 1; i < controllerMappingHyphens.length; i++) {
          controllerMappingHyphenStr.append(StringUtils.capitalize(controllerMappingHyphens[i]));
        }
        objectMap.put("controllerMappingHyphen", controllerMappingHyphenStr.append("s").toString());
      }

      this.outputFile(new File(fileName), objectMap, value + ".ftl");
    });
  }
}

自定义模板 RESTful(FreeMarker)src/main/resources/template/generator

controller.java.ftl

package ${package.Controller};

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import ${package.Entity}.${entity};
import ${package.Entity}.qo.*;
import ${package.Entity}.vo.*;
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
import ${package.Service}.${table.serviceName};
import ${package.Parent}.struct.${entity}Struct;
import ${package.Parent}.tools.domain.response.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

/**
 * ${table.comment!} Controller
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
@Api(tags = "${table.comment!}管理")
@Slf4j
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>

  @Autowired
  private ${table.serviceName} ${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)};

  @ApiOperation(value = "${table.comment!}列表", notes = "${table.comment!}列表")
  @GetMapping
  public Result<List<${entity}ListVO>> list(@Validated ${entity}ListQO query) {
    try {
      startPage(query);
      return getTableList(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.list(new LambdaQueryWrapper<${entity}>()
        .orderByDesc(${entity}::getUpdatedTime)
      ));
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      return failed("${table.comment!}列表查询异常", e);
    }
  }

  @ApiOperation(value = "${table.comment!}详情", notes = "${table.comment!}详情")
  @GetMapping("/{id}")
  public Result<${entity}GetVO> get(@Validated ${entity}GetQO query) {
    try {
      return ok(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.getById(query.getId()));
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      return failed("${table.comment!}详情查询异常", e);
    }
  }

  @ApiOperation(value = "保存${table.comment!}", notes = "保存${table.comment!}")
  @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
  @PostMapping
  public Result save(@RequestBody @Validated ${entity}SaveQO obj) {
    try {
      return custom(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.save(${entity}Struct.INSTANCE.to(obj)), SAVE_FAILED_MSG);
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      return failed("${table.comment!}保存异常", e);
    }
  }

  @ApiOperation(value = "更新${table.comment!}", notes = "更新${table.comment!}")
  @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
  @PutMapping("/{id}")
  public Result update(@RequestBody @Validated ${entity}UpdateQO obj) {
    try {
      return custom(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.updateById(${entity}Struct.INSTANCE.to(obj)), UPDATE_FAILED_MSG);
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      return failed("${table.comment!}更新异常", e);
    }
  }

  @ApiOperation(value = "删除${table.comment!}", notes = "删除${table.comment!}")
  @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
  @DeleteMapping("/{ids}")
  public Result remove(@Validated ${entity}RemoveQO query) {
    try {
      return custom(${table.serviceName?substring(1, 2)?lower_case}${table.serviceName?substring(2)}.removeByIds(Arrays.asList(query.getIds().split(","))), REMOVE_FAILED_MSG);
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      return failed("${table.comment!}删除异常", e);
    }
  }
}
</#if>

entity.java.ftl

package ${package.Entity};

<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#--<#if swagger2??>-->
<#--import io.swagger.annotations.ApiModel;-->
<#--import io.swagger.annotations.ApiModelProperty;-->
<#--</#if>-->
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
  <#if chainModel>
import lombok.experimental.Accessors;
  </#if>
</#if>

/**
 * ${table.comment!}
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if table.convert>
@TableName("${table.name}")
</#if>
<#if entityLombokModel>
  <#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
  <#else>
@EqualsAndHashCode(callSuper = false)
  </#if>
  <#if chainModel>
@Accessors(chain = true)
  </#if>
@Data
</#if>
<#--<#if swagger2??>-->
<#--@ApiModel(value = "${entity} 实体", description = "${table.comment!}")-->
<#--</#if>-->
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#else>
public class ${entity} <#if entitySerialVersionUID>implements Serializable</#if> {
</#if>

<#if entitySerialVersionUID>
  private static final long serialVersionUID = 1L;

</#if>
<#-- ----------  BEGIN 字段循环遍历  -------- -->
<#list table.fields as field>
  <#-- 排除公共字段 -->
<#--  <#if field.propertyName != 'id' && field.propertyName != 'delFlag' && field.propertyName != 'createdTime' && field.propertyName != 'createdBy' && field.propertyName != 'updatedTime' && field.propertyName != 'updatedBy' && field.propertyName != 'remark'>-->
  <#if field.keyFlag>
    <#assign keyPropertyName="${field.propertyName}"/>
  </#if>
  <#if field.comment!?length gt 0>
<#--    <#if swagger2??>-->
<#--  @ApiModelProperty("${field.comment}")-->
<#--    <#else>-->
  /**
   * ${field.comment}
   */
<#--    </#if>-->
  </#if>
  <#if field.keyFlag>
    <#-- 主键 -->
    <#if field.keyIdentityFlag>
  @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
    <#elseif idType??>
  @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
    <#elseif field.convert>
  @TableId("${field.annotationColumnName}")
    </#if>
    <#-- 普通字段 -->
  <#elseif field.fill??>
  <#-- -----   存在字段填充设置   ----->
    <#if field.convert>
  @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
    <#else>
  @TableField(fill = FieldFill.${field.fill})
    </#if>
  <#elseif field.convert>
  @TableField("${field.annotationColumnName}")
  </#if>
  <#-- 乐观锁注解 -->
  <#if field.versionField>
  @Version
  </#if>
  <#-- 逻辑删除注解 -->
  <#if field.logicDeleteField>
  @TableLogic
  </#if>
  private ${field.propertyType} ${field.propertyName};
<#--  </#if>-->
</#list>
<#-- ----------  END 字段循环遍历  -------- -->
<#-- Lombok 模式 -->
<#if !entityLombokModel>
  <#list table.fields as field>
    <#if field.propertyType == "boolean">
      <#assign getprefix="is"/>
    <#else>
      <#assign getprefix="get"/>
    </#if>
  public ${field.propertyType} ${getprefix}${field.capitalName}() {
    return ${field.propertyName};
  }

  <#if chainModel>
  public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  <#else>
  public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  </#if>
    this.${field.propertyName} = ${field.propertyName};
    <#if chainModel>
    return this;
    </#if>
  }
  </#list>
</#if>
<#-- 列常量 -->
<#if entityColumnConstant>
  <#list table.fields as field>
  public static final String ${field.name?upper_case} = "${field.name}";

  </#list>
</#if>
<#if activeRecord>
  @Override
  protected Serializable pkVal() {
  <#if keyPropertyName??>
    return this.${keyPropertyName};
  <#else>
    return null;
  </#if>
  }

</#if>
<#if !entityLombokModel>
  @Override
  public String toString() {
    return "${entity}{" +
  <#list table.fields as field>
    <#if field_index==0>
      "${field.propertyName}=" + ${field.propertyName} +
    <#else>
      ", ${field.propertyName}=" + ${field.propertyName} +
    </#if>
  </#list>
    "}";
  }
</#if>
}

entityQO.java.ftl,生成后需自己调整字段

package ${package.Entity}.qo;

<#if fileNameSuffix?contains('ListQO')>
import com.irms.domain.entity.PageLimit;
</#if>
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger??>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
  <#if chainModel>
import lombok.experimental.Accessors;
  </#if>
</#if>

import javax.validation.constraints.*;

/**
 * ${table.comment!}<#if fileNameSuffix?contains('List')>列表查询对象<#elseif fileNameSuffix?contains('Get')>详情查询对象<#elseif fileNameSuffix?contains('Save')>保存对象<#elseif fileNameSuffix?contains('Update')>更新对象<#elseif fileNameSuffix?contains('Remove')>删除对象<#else></#if>
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if entityLombokModel>
  <#if fileNameSuffix?contains('ListQO')>
@EqualsAndHashCode(callSuper = true)
  <#else>
@EqualsAndHashCode(callSuper = false)
  </#if>
  <#if chainModel>
@Accessors(chain = true)
  </#if>
@Data
</#if>
<#if swagger??>
@ApiModel(value = "${entity} <#if fileNameSuffix?contains('List')>列表查询对象<#elseif fileNameSuffix?contains('Get')>详情查询对象<#elseif fileNameSuffix?contains('Save')>保存对象<#elseif fileNameSuffix?contains('Update')>更新对象<#elseif fileNameSuffix?contains('Remove')>删除对象<#else></#if>", description = "${table.comment!}<#if fileNameSuffix?contains('List')>列表查询对象<#elseif fileNameSuffix?contains('Get')>详情查询对象<#elseif fileNameSuffix?contains('Save')>保存对象<#elseif fileNameSuffix?contains('Update')>更新对象<#elseif fileNameSuffix?contains('Remove')>删除对象<#else></#if>")
</#if>
<#if fileNameSuffix?contains("ListQO")>
public class ${entity}${fileNameSuffix} extends PageLimit {
<#elseif activeRecord>
public class ${entity}${fileNameSuffix} extends Model<${entity}> {
<#else>
public class ${entity}${fileNameSuffix} <#if entitySerialVersionUID>implements Serializable</#if> {
</#if>

<#if entitySerialVersionUID>
  private static final long serialVersionUID = 1L;

</#if>
<#-- 如果包含是删除,添加多个 ID 的字段 -->
<#if fileNameSuffix?contains('Remove')>
  @ApiModelProperty("多个${table.comment!} ID,逗号分隔")
  private String ids;

</#if>
<#-- 临时写法 -->
<#if fileNameSuffix?contains('Get') || fileNameSuffix?contains('Update')>
  @ApiModelProperty("主键")
  @Min(value = 1, message = "${table.comment!} ID 错误")
  private Long id;

</#if>
<#-- ----------  BEGIN 字段循环遍历  -------- -->
<#list table.fields as field>
  <#-- 排除公共字段 -->
  <#if (field.propertyName!='id' && fileNameSuffix?contains('Get')) || (field.propertyName!='delFlag'&&field.propertyName!='createdTime'&&field.propertyName!='createdBy'&&field.propertyName!='updatedTime'&&field.propertyName!='updatedBy'&&field.propertyName!='remark')>
  <#if field.keyFlag>
    <#assign keyPropertyName="${field.propertyName}"/>
  </#if>
  <#if field.comment!?length gt 0>
    <#if swagger??>
  @ApiModelProperty("${field.comment}")
      <#-- 校验 -->
      <#if field.propertyType == 'Long'>
  @NotNull(message = "${field.comment}不能为空")
  @Min(value = 1, message = "${field.comment} 错误")
      <#elseif field.propertyType == 'String'>
  @NotBlank(message = "${field.comment}不能为空")
      <#else>
      </#if>
    <#else>
  /**
   * ${field.comment}
   */
      <#-- 校验 -->
      <#if field.propertyType == 'Long'>
  @NotNull(message = "${field.comment}不能为空")
  @Min(value = 1, message = "${field.comment} 错误")
      <#elseif field.propertyType == 'String'>
  @NotBlank(message = "${field.comment}不能为空")
      <#else>
      </#if>
    </#if>
  </#if>
  <#if field.keyFlag>
    <#-- 主键 -->
    <#if field.keyIdentityFlag>
  @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
    <#elseif idType??>
  @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
    <#elseif field.convert>
  @TableId("${field.annotationColumnName}")
    </#if>
    <#-- 普通字段 -->
  <#elseif field.fill??>
  <#-- -----   存在字段填充设置   ----->
    <#if field.convert>
  @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
    <#else>
  @TableField(fill = FieldFill.${field.fill})
    </#if>
  <#elseif field.convert>
  @TableField("${field.annotationColumnName}")
  </#if>
  <#-- 乐观锁注解 -->
  <#if field.versionField>
  @Version
  </#if>
  <#-- 逻辑删除注解 -->
  <#if field.logicDeleteField>
  @TableLogic
  </#if>
  private ${field.propertyType} ${field.propertyName};

  </#if>
</#list>
<#-- ----------  END 字段循环遍历  -------- -->
<#-- Lombok 模式 -->
<#if !entityLombokModel>
  <#list table.fields as field>
    <#if field.propertyType == "boolean">
      <#assign getprefix="is"/>
    <#else>
      <#assign getprefix="get"/>
    </#if>
  public ${field.propertyType} ${getprefix}${field.capitalName}() {
    return ${field.propertyName};
  }

  <#if chainModel>
  public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  <#else>
  public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  </#if>
    this.${field.propertyName} = ${field.propertyName};
    <#if chainModel>
    return this;
    </#if>
  }
  </#list>
</#if>
<#-- 列常量 -->
<#if entityColumnConstant>
  <#list table.fields as field>
  public static final String ${field.name?upper_case} = "${field.name}";

  </#list>
</#if>
<#if activeRecord>
  @Override
  protected Serializable pkVal() {
  <#if keyPropertyName??>
    return this.${keyPropertyName};
  <#else>
    return null;
  </#if>
  }

</#if>
<#if !entityLombokModel>
  @Override
  public String toString() {
    return "${entity}{" +
  <#list table.fields as field>
    <#if field_index==0>
      "${field.propertyName}=" + ${field.propertyName} +
    <#else>
      ", ${field.propertyName}=" + ${field.propertyName} +
    </#if>
  </#list>
    "}";
  }
</#if>
}

entityVO.java.ftl,生成后需自己调整字段

package ${package.Entity}.vo;

<#if fileNameSuffix?contains('ListVO') || fileNameSuffix?contains('GetVO')>
import com.irms.domain.vo.BaseVO;
</#if>
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger??>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
  <#if chainModel>
import lombok.experimental.Accessors;
  </#if>
</#if>

/**
 * ${table.comment!}<#if fileNameSuffix?contains('List')>列表展示对象<#elseif fileNameSuffix?contains('Get')>详情展示对象<#else></#if>
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if table.convert>
  @TableName("${table.name}")
</#if>
<#if entityLombokModel>
  <#if fileNameSuffix?contains('ListVO') || fileNameSuffix?contains('GetVO')>
@EqualsAndHashCode(callSuper = true)
  <#else>
@EqualsAndHashCode(callSuper = false)
  </#if>
  <#if chainModel>
@Accessors(chain = true)
  </#if>
@Data
</#if>
<#if swagger??>
@ApiModel(value = "${entity} <#if fileNameSuffix?contains('List')>列表展示对象<#elseif fileNameSuffix?contains('Get')>详情展示对象<#else></#if>", description = "${table.comment!}<#if fileNameSuffix?contains('List')>列表展示对象<#elseif fileNameSuffix?contains('Get')>详情展示对象<#else></#if>")
</#if>
<#if fileNameSuffix?contains("ListVO") || fileNameSuffix?contains('GetVO')>
public class ${entity + fileNameSuffix} extends BaseVO {
<#elseif activeRecord>
public class ${entity + fileNameSuffix} extends Model<${entity}> {
<#else>
public class ${entity + fileNameSuffix} <#if entitySerialVersionUID>implements Serializable</#if> {
</#if>

<#if entitySerialVersionUID>
  private static final long serialVersionUID = 1L;

</#if>
<#-- ----------  BEGIN 字段循环遍历  -------- -->
<#list table.fields as field>
  <#-- 排除公共字段 -->
  <#if field.propertyName != 'id' && field.propertyName != 'delFlag' && field.propertyName != 'createdTime' && field.propertyName != 'createdBy' && field.propertyName != 'updatedTime' && field.propertyName != 'updatedBy' && field.propertyName != 'remark'>
  <#if field.keyFlag>
    <#assign keyPropertyName="${field.propertyName}"/>
  </#if>
  <#if field.comment!?length gt 0>
    <#if swagger??>
  @ApiModelProperty("${field.comment}")
    <#else>
  /**
   * ${field.comment}
   */
    </#if>
  </#if>
  <#if field.keyFlag>
    <#-- 主键 -->
    <#if field.keyIdentityFlag>
  @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
    <#elseif idType??>
  @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
    <#elseif field.convert>
  @TableId("${field.annotationColumnName}")
    </#if>
    <#-- 普通字段 -->
  <#elseif field.fill??>
  <#-- -----   存在字段填充设置   ----->
    <#if field.convert>
  @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
    <#else>
  @TableField(fill = FieldFill.${field.fill})
    </#if>
  <#elseif field.convert>
  @TableField("${field.annotationColumnName}")
  </#if>
  <#-- 乐观锁注解 -->
  <#if field.versionField>
  @Version
  </#if>
  <#-- 逻辑删除注解 -->
  <#if field.logicDeleteField>
  @TableLogic
  </#if>
  private ${field.propertyType} ${field.propertyName};

  </#if>
</#list>
<#-- ----------  END 字段循环遍历  -------- -->
<#-- Lombok 模式 -->
<#if !entityLombokModel>
  <#list table.fields as field>
    <#if field.propertyType == "boolean">
      <#assign getprefix="is"/>
    <#else>
      <#assign getprefix="get"/>
    </#if>
  public ${field.propertyType} ${getprefix}${field.capitalName}() {
    return ${field.propertyName};
  }

  <#if chainModel>
  public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  <#else>
  public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
  </#if>
    this.${field.propertyName} = ${field.propertyName};
    <#if chainModel>
    return this;
    </#if>
  }
  </#list>
</#if>
<#-- 列常量 -->
<#if entityColumnConstant>
  <#list table.fields as field>
  public static final String ${field.name?upper_case} = "${field.name}";

  </#list>
</#if>
<#if activeRecord>
  @Override
  protected Serializable pkVal() {
  <#if keyPropertyName??>
    return this.${keyPropertyName};
  <#else>
    return null;
  </#if>
  }

</#if>
<#if !entityLombokModel>
  @Override
  public String toString() {
    return "${entity}{" +
  <#list table.fields as field>
    <#if field_index==0>
      "${field.propertyName}=" + ${field.propertyName} +
    <#else>
      ", ${field.propertyName}=" + ${field.propertyName} +
    </#if>
  </#list>
    "}";
  }
</#if>
}

mapper.java.ftl

package ${package.Mapper};

import ${package.Entity}.${entity};
import ${superMapperClassPackage};

/**
 * ${table.comment!} Mapper
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if kotlin>
interface ${table.mapperName} : ${superMapperClass}<${entity}>
<#else>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {

}
</#if>

service.java

package ${package.Service};

import ${package.Entity}.${entity};
import ${superServiceClassPackage};

/**
 * ${table.comment!} 服务
 *
<#if author != "">
 * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {

}
</#if>

serviceImpl.java.ftl

package ${package.ServiceImpl};

import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * ${table.comment!} 服务实现
 *
<#if author != "">
  * @author ${author}
</#if>
<#if date != "">
 * @since ${date}
</#if>
 */
@Service
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {

}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {

  @Autowired
  private ${table.mapperName} ${table.mapperName?substring(0, 1)?lower_case}${table.mapperName?substring(1)};

}
</#if>

struct.java.ftl

package ${package.Parent};

import ${package.Entity}.${entity};
import ${package.Entity}.qo.*;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ${entity}Struct {

  ${entity}Struct INSTANCE = Mappers.getMapper(${entity}Struct.class);

  ${entity} to(${entity}SaveQO obj);

  ${entity} to(${entity}UpdateQO obj);
}

0

评论区