代码生成模板自定义与扩展指南

作者:忆笙智云官方 | 发布时间:2026-06-20 14:00 | 更新时间:2026-07-20 14:00

代码生成模板自定义与扩展指南

引言

代码生成是低代码平台的核心能力,而模板引擎是代码生成的灵魂。通过自定义模板,开发者可以根据团队规范、项目架构和业务需求,快速生成标准化的代码骨架,大幅减少重复编码工作。

本文将对比 FreeMarker、Velocity、Thymeleaf 三种主流模板引擎,介绍自定义模板开发规范、模板集管理、参数配置和版本管理的最佳实践。

核心内容

一、三种模板引擎对比

graph TD
    A[模板引擎选型] --> B[FreeMarker]
    A --> C[Velocity]
    A --> D[Thymeleaf]

    B --> B1[功能最强大]
    B --> B2[语法灵活]
    B --> B3[社区活跃]

    C --> C1[语法最简洁]
    C --> C2[学习成本低]
    C --> C3[功能较基础]

    D --> D1[自然模板]
    D --> D2[浏览器可预览]
    D --> D3[适合HTML生成]

    style B fill:#c8e6c9
特性 FreeMarker Velocity Thymeleaf
语法复杂度 中等 简单 中等
功能丰富度 ★★★★★ ★★★ ★★★★
宏/函数支持 强大 基础 中等
条件/循环 完善 完善 完善
输出转义 自动 手动 自动
模板继承 支持 不支持 支持
浏览器预览 不支持 不支持 支持
Spring集成 良好 一般 原生
代码生成适用 ★★★★★ ★★★★ ★★★

推荐:代码生成场景首选 FreeMarker,功能强大且生态成熟。

二、FreeMarker 模板开发规范

1. 模板目录结构

templates/
├── java/
│   ├── controller.ftl          # Controller模板
│   ├── service.ftl             # Service接口模板
│   ├── serviceImpl.ftl         # Service实现模板
│   ├── mapper.ftl              # Mapper接口模板
│   ├── entity.ftl              # 实体类模板
│   ├── dto/
│   │   ├── createDTO.ftl       # 创建DTO模板
│   │   └── updateDTO.ftl       # 更新DTO模板
│   └── vo.ftl                  # VO模板
├── vue/
│   ├── api.ftl                 # API请求模板
│   ├── index.ftl               # 页面模板
│   └── components/
│       ├── form.ftl            # 表单组件模板
│       └── table.ftl           # 表格组件模板
├── xml/
│   └── mapper.ftl              # MyBatis XML模板
└── config/
    └── template-config.json    # 模板配置文件

2. 模板配置文件

{
  "templateSet": {
    "code": "standard-crud",
    "name": "标准CRUD模板集",
    "description": "生成完整的CRUD代码,包含前后端",
    "version": "1.0.0",
    "templates": [
      {
        "id": "controller",
        "name": "Controller",
        "templatePath": "java/controller.ftl",
        "outputPath": "${basePackagePath}/controller/${className}Controller.java",
        "fileType": "java"
      },
      {
        "id": "service",
        "name": "Service接口",
        "templatePath": "java/service.ftl",
        "outputPath": "${basePackagePath}/service/${className}Service.java",
        "fileType": "java"
      },
      {
        "id": "serviceImpl",
        "name": "Service实现",
        "templatePath": "java/serviceImpl.ftl",
        "outputPath": "${basePackagePath}/service/impl/${className}ServiceImpl.java",
        "fileType": "java"
      },
      {
        "id": "vue-page",
        "name": "Vue页面",
        "templatePath": "vue/index.ftl",
        "outputPath": "views/${moduleName}/${businessName}/index.vue",
        "fileType": "vue"
      }
    ],
    "params": [
      {
        "code": "basePackage",
        "name": "基础包路径",
        "type": "STRING",
        "defaultValue": "com.example",
        "required": true
      },
      {
        "code": "moduleName",
        "name": "模块名称",
        "type": "STRING",
        "required": true
      },
      {
        "code": "useRecord",
        "name": "使用Record类",
        "type": "BOOLEAN",
        "defaultValue": true
      },
      {
        "code": "author",
        "name": "作者",
        "type": "STRING",
        "defaultValue": "CodeGenerator"
      }
    ]
  }
}

3. Controller 模板示例

<#-- Controller模板 -->
package ${basePackage}.${moduleName}.controller;

import ${basePackage}.${moduleName}.service.${className}Service;
import ${basePackage}.${moduleName}.dto.${className}CreateDTO;
import ${basePackage}.${moduleName}.dto.${className}UpdateDTO;
import ${basePackage}.${moduleName}.vo.${className}VO;
import ${basePackage}.common.core.Result;
import ${basePackage}.common.core.PageResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
 * ${tableComment}管理
 *
 * @author ${author}
 * @date ${.now?string('yyyy-MM-dd')}
 */
@RestController
@RequestMapping("/api/${moduleName}/${businessName}")
@Tag(name = "${tableComment}管理")
@RequiredArgsConstructor
public class ${className}Controller {

    private final ${className}Service ${className?uncap_first}Service;

    @Operation(summary = "分页查询")
    @GetMapping("/page")
    public Result<PageResult<${className}VO>> pageQuery(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            <#list queryFields as field>
            @RequestParam(required = false) ${field.javaType} ${field.fieldName}<#if field?has_next>,</#if>
            </#list>) {
        return Result.success(${className?uncap_first}Service.pageQuery(page, size<#list queryFields as field>, ${field.fieldName}</#list>));
    }

    @Operation(summary = "根据ID查询")
    @GetMapping("/{id}")
    public Result<${className}VO> getById(@PathVariable Long id) {
        return Result.success(${className?uncap_first}Service.getById(id));
    }

    @Operation(summary = "创建")
    @PostMapping
    public Result<Void> create(@RequestBody @Valid ${className}CreateDTO dto) {
        ${className?uncap_first}Service.create(dto);
        return Result.success();
    }

    @Operation(summary = "更新")
    @PutMapping
    public Result<Void> update(@RequestBody @Valid ${className}UpdateDTO dto) {
        ${className?uncap_first}Service.update(dto);
        return Result.success();
    }

    @Operation(summary = "删除")
    @DeleteMapping("/{id}")
    public Result<Void> delete(@PathVariable Long id) {
        ${className?uncap_first}Service.delete(id);
        return Result.success();
    }
}

4. Entity 模板示例(支持Record)

<#if useRecord>
<#-- Record类模板 -->
package ${basePackage}.${moduleName}.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;

/**
 * ${tableComment}实体
 *
 * @author ${author}
 * @date ${.now?string('yyyy-MM-dd')}
 */
@TableName("${tableName}")
public record ${className}(
    <#list fields as field>
    <#if field.primaryKey>
    @TableId(type = IdType.ASSIGN_ID)
    </#if>
    <#if field.columnName != field.fieldName>
    @TableField("${field.columnName}")
    </#if>
    ${field.javaType} ${field.fieldName}<#if field?has_next>,</#if>

    </#list>
) {}
<#else>
<#-- 传统类模板 -->
package ${basePackage}.${moduleName}.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;

/**
 * ${tableComment}实体
 *
 * @author ${author}
 * @date ${.now?string('yyyy-MM-dd')}
 */
@Data
@Accessors(chain = true)
@TableName(value = "${tableName}"<#if hasJsonField>, autoResultMap = true</#if>)
public class ${className} {

    <#list fields as field>
    <#if field.primaryKey>
    @TableId(type = IdType.ASSIGN_ID)
    </#if>
    <#if field.columnName != field.fieldName>
    @TableField("${field.columnName}"<#if field.jsonField>, typeHandler = JacksonTypeHandler.class</#if>)
    </#if>
    /** ${field.comment} */
    private ${field.javaType} ${field.fieldName};

    </#list>
}
</#if>

三、模板引擎集成

/** 模板引擎服务 */
@Service
public class TemplateEngineService {

    private final Configuration freemarkerConfig;

    public TemplateEngineService() throws IOException {
        this.freemarkerConfig = new Configuration(Configuration.VERSION_2_3_32);
        this.freemarkerConfig.setDirectoryForTemplateLoading(
            new File("templates/"));
        this.freemarkerConfig.setDefaultEncoding("UTF-8");
        this.freemarkerConfig.setTemplateExceptionHandler(
            TemplateExceptionHandler.RETHROW_HANDLER);
    }

    /**
     * 渲染模板
     * @param templateName 模板名称
     * @param dataModel 数据模型
     * @return 渲染结果
     */
    public String render(String templateName, Map<String, Object> dataModel) {
        try {
            Template template = freemarkerConfig.getTemplate(templateName);
            StringWriter writer = new StringWriter();
            template.process(dataModel, writer);
            return writer.toString();
        } catch (TemplateException | IOException e) {
            throw new RuntimeException("模板渲染失败: " + templateName, e);
        }
    }

    /**
     * 批量渲染模板集
     * @param templateSet 模板集配置
     * @param dataModel 数据模型
     * @return 渲染结果列表
     */
    public List<GeneratedFile> renderTemplateSet(
            TemplateSetConfig templateSet,
            Map<String, Object> dataModel) {
        List<GeneratedFile> results = new ArrayList<>();

        for (TemplateConfig template : templateSet.getTemplates()) {
            String content = render(template.getTemplatePath(), dataModel);
            String outputPath = renderOutputPath(template.getOutputPath(), dataModel);

            results.add(new GeneratedFile(
                template.getName(),
                outputPath,
                content,
                template.getFileType()
            ));
        }

        return results;
    }

    /** 渲染输出路径中的变量 */
    private String renderOutputPath(String pathTemplate, Map<String, Object> dataModel) {
        String result = pathTemplate;
        for (Map.Entry<String, Object> entry : dataModel.entrySet()) {
            result = result.replace("${" + entry.getKey() + "}",
                String.valueOf(entry.getValue()));
        }
        return result;
    }
}

四、数据模型构建

graph TD
    A[数据库表元数据] --> B[数据模型构建器]
    C[用户配置参数] --> B
    D[模板集配置] --> B

    B --> E[数据模型]

    E --> F[className]
    E --> G[tableName]
    E --> H[fields列表]
    E --> I[basePackage]
    E --> J[moduleName]
    E --> K[自定义参数]

    F --> L[模板渲染]
    G --> L
    H --> L
    I --> L
    J --> L
    K --> L
/** 代码生成数据模型构建器 */
@Service
public class CodegenDataModelBuilder {

    /**
     * 构建代码生成数据模型
     * @param tableInfo 数据库表信息
     * @param params 用户配置参数
     * @return 数据模型
     */
    public Map<String, Object> build(TableInfo tableInfo, CodegenParams params) {
        Map<String, Object> model = new HashMap<>();

        // 基础信息
        model.put("className", toClassName(tableInfo.getTableName()));
        model.put("tableName", tableInfo.getTableName());
        model.put("tableComment", tableInfo.getTableComment());
        model.put("businessName", toBusinessName(tableInfo.getTableName()));
        model.put("moduleName", params.getModuleName());
        model.put("basePackage", params.getBasePackage());
        model.put("basePackagePath", params.getBasePackage().replace(".", "/"));
        model.put("author", params.getAuthor());
        model.put("useRecord", params.isUseRecord());

        // 字段信息
        List<FieldInfo> fields = tableInfo.getColumns().stream()
            .map(this::convertField)
            .toList();
        model.put("fields", fields);

        // 查询字段(用于分页查询参数)
        List<FieldInfo> queryFields = fields.stream()
            .filter(f -> f.isQueryable())
            .toList();
        model.put("queryFields", queryFields);

        // 表单字段(用于创建/更新DTO)
        List<FieldInfo> formFields = fields.stream()
            .filter(f -> !f.isPrimaryKey() && !f.isAutoFill())
            .toList();
        model.put("formFields", formFields);

        return model;
    }

    /** 表名转类名:sys_user -> SysUser */
    private String toClassName(String tableName) {
        String[] parts = tableName.split("_");
        StringBuilder sb = new StringBuilder();
        for (String part : parts) {
            if (part.isEmpty()) continue;
            sb.append(Character.toUpperCase(part.charAt(0)))
              .append(part.substring(1));
        }
        return sb.toString();
    }
}

五、模板集管理

graph TD
    A[模板集管理] --> B[模板集列表]
    A --> C[模板集详情]
    A --> D[模板集版本]

    B --> B1[标准CRUD模板集]
    B --> B2[树形结构模板集]
    B --> B3[主子表模板集]

    C --> C1[包含模板列表]
    C --> C2[参数配置]
    C --> C3[输出路径规则]

    D --> D1[v1.0 初始版本]
    D --> D2[v1.1 修复问题]
    D --> D3[v2.0 架构升级]
/** 模板集管理服务 */
@Service
public class TemplateSetService {

    /**
     * 获取模板集列表
     */
    public List<TemplateSetVO> listTemplateSets() {
        // 从数据库或文件系统加载模板集列表
        return templateSetMapper.selectList(null).stream()
            .map(this::toVO)
            .toList();
    }

    /**
     * 预览代码生成结果
     * 不实际生成文件,仅返回预览内容
     */
    public List<GeneratedFile> preview(Long templateSetId, CodegenParams params) {
        TemplateSetConfig config = getTemplateSetConfig(templateSetId);
        Map<String, Object> dataModel = dataModelBuilder.build(
            getTableInfo(params.getTableId()), params);
        return templateEngineService.renderTemplateSet(config, dataModel);
    }

    /**
     * 执行代码生成
     * 将生成的代码写入指定目录
     */
    public void generate(Long templateSetId, CodegenParams params, String outputDir) {
        List<GeneratedFile> files = preview(templateSetId, params);

        for (GeneratedFile file : files) {
            Path filePath = Paths.get(outputDir, file.getOutputPath());
            // 确保目录存在
            Files.createDirectories(filePath.getParent());
            // 写入文件
            Files.writeString(filePath, file.getContent());
        }
    }
}

六、模板版本管理

/** 模板版本实体 */
public record TemplateVersion(
    Long id,
    String templateCode,
    String version,
    String content,
    String changeLog,
    Boolean isActive,
    LocalDateTime createTime
) {}

/** 模板版本管理服务 */
@Service
public class TemplateVersionService {

    /**
     * 发布新版本
     */
    @Transactional
    public void publishVersion(String templateCode, String content, String changeLog) {
        // 将当前活跃版本置为非活跃
        templateVersionMapper.deactivateByCode(templateCode);

        // 创建新版本
        TemplateVersion version = new TemplateVersion(
            null,
            templateCode,
            generateNextVersion(templateCode),
            content,
            changeLog,
            true,
            LocalDateTime.now()
        );
        templateVersionMapper.insert(version);
    }

    /**
     * 回滚到指定版本
     */
    @Transactional
    public void rollback(String templateCode, String targetVersion) {
        templateVersionMapper.deactivateByCode(templateCode);
        templateVersionMapper.activateByCodeAndVersion(templateCode, targetVersion);
    }
}

结论与建议

模板开发规范

  1. 命名规范:模板文件名使用小写+连字符,如 service-impl.ftl;变量使用驼峰命名。

  2. 注释规范:模板文件头部添加说明注释,包含模板用途、参数说明、作者信息。

  3. 条件控制:使用 <#if> 控制可选代码段,通过参数开关控制生成内容。

  4. 格式规范:模板中的缩进与目标语言规范一致,避免生成代码格式混乱。

  5. 安全输出:使用 ${variable!} 处理空值,使用 ?html?string 转义输出。

最佳实践建议

  1. 模板集而非单模板:按业务场景组织模板集,一个模板集包含前后端所有相关文件。

  2. 预览优先:生成代码前先预览,确认无误后再写入文件。

  3. 不覆盖已有文件:生成代码时检测文件是否已存在,已存在则跳过或提示用户确认。

  4. 版本回滚:保留模板历史版本,支持一键回滚。

  5. 参数化一切:所有可变内容都应通过参数控制,模板中不硬编码任何业务数据。

相关资源