05-工具优化

  1. 可移植性优化
  2. 功能优化
  3. 健壮性优化
  4. 可扩展性优化

1. 可移植性优化

1. 什么是可移植性

  • 程序的可移植性:指程序在不同计算机、操作系统或编程语言环境下能够正确运行的能力。具有良好可移植性的程序能够在不同的环境中轻松运行,而不需要大量的修改或适应工作
  • MainGenerator 生成代码时依赖的 inputRootPath(模板文件路径)是固定的
bd3d2c713c85486192b2ca25535fd89b

2. 实现方式

核心思路:把绝对路径改为相对路径。只需要把代码生成器依赖的模板文件移动到生成后的代码生成器目录下

  • 已经定义的 fileConfig.inputRootPath,模板文件路径改为相对路径
  • 新增 fileConfig.sourceRootPath 字段,表示模板文件所在的原始路径
"fileConfig": {
    "inputRootPath": ".source/acm-template",
    "outputRootPath": "generated",
    "sourceRootPath": "/Users/listao/mca/listao_generator/02-tool-generator/origin/acm-template",
    "type": "dir",

Meta.java 实体类,增加 sourceRootPath 字段

@NoArgsConstructor
@Data
public static class FileConfig {
    private String inputRootPath;
    private String outputRootPath;
    private String sourceRootPath;
    private String type;
    private List<FileInfo> files;
    // ...
}

增加复制原始模板文件的逻辑,在所有生成代码操作之前

public class T_Main {

    /**
     * `@Test` 运行需要 test/resources/templates
     */
    public static void main(String[] args) throws IOException, InterruptedException, TemplateException {
        Meta meta = MetaManager.getMetaObject();

        // 1. 输出根路径
        String projectPath = System.getProperty("user.dir");
        String outputPath = projectPath + "/.tmp/" + meta.getName();
        if (!FileUtil.exist(outputPath)) {
            FileUtil.mkdir(outputPath);
        }

        // 复制原始文件
        String sourceRootPath = meta.getFileConfig().getSourceRootPath();
        String sourceCopyDestPath = outputPath + File.separator + ".source";
        FileUtil.copy(sourceRootPath, sourceCopyDestPath, false);

        // 2. 读取 resources 目录
        ClassPathResource classPathResource = new ClassPathResource("");
        String inputResourcePath = classPathResource.getAbsolutePath();
















 
 
 




成功在生成的项目中复制了原始模板文件:

45593c7b44b34174a7d3b952ded8f662

2. 功能优化

1. 增加 README.md

  • 根目录下 README.md 项目介绍文件,可以帮助用户快速了解整个项目的背景、价值、用法、参与方式等,还可以加上制作工具作者的水印,为项目引流
  • 读取元信息并使用 FreeMarker 动态生成即可
# ${name}

> ${description}
>
> 作者:${author}
>
> 基于 [程序员谷牛](https://yuyuanweb.feishu.cn/wiki/Abldw5WkjidySxkKxU2cQdAtnah)[鱼籽代码生成器项目](https://github.com/liyupi/yuzi-generator) 制作,感谢您的使用!

可以通过命令行交互式输入的方式动态生成想要的项目代码

## 使用说明

执行项目根目录下的脚本文件:

```
generator <命令> <选项参数>
```

示例命令:

```
generator generate<#list modelConfig.models as modelInfo><#if modelInfo.abbr??> -${modelInfo.abbr}</#if></#list>
```

## 参数说明

<#list modelConfig.models as modelInfo>
${modelInfo?index + 1})${modelInfo.fieldName!"未命名"}

类型:${modelInfo.type}

描述:${modelInfo.description}

<#if modelInfo.defaultValue??>
默认值:${modelInfo.defaultValue?c}
</#if>

<#if modelInfo.abbr??>
缩写: -${modelInfo.abbr}
</#if>

</#list>
// 8. README.md
inputFilePath = inputResourcePath + File.separator + "templates/README.md.ftl";
outputFilePath = outputPath + File.separator + "README.md";
DynamicFileGenerator.doGenerate(inputFilePath , outputFilePath, meta);

2. 制作精简版生成器

生成更为精简的生成器,只需要保留 jar 包、脚本文件、原始模板文件,其他的都不保留

34d2c56defdf4cac9c13c35db5e27a32

不修改原有代码生成方式,而是额外生成一套精简版的生成器,放到 dist 目录下,便于后续调试、或交给用户自己选择生成方式

// 9. 生成精简版的程序
// 封装脚本
String shellOutputFilePath = outputPath + File.separator + "generator";
String jarName = String.format("%s-%s-jar-with-dependencies.jar", meta.getName(), meta.getVersion());
String jarPath = "target/" + jarName;
ScriptGenerator.doGenerate(shellOutputFilePath, jarPath);

// 10. jar 生成
JarGenerator.doGenerate(outputPath);

// 11. dist 生成
String distOutputPath = outputPath + "-dist";
// - 拷贝 jar 包
String targetAbsolutePath = distOutputPath + File.separator + "target";
FileUtil.mkdir(targetAbsolutePath);
String jarAbsolutePath = outputPath + File.separator + jarPath;
FileUtil.copy(jarAbsolutePath, targetAbsolutePath, true);
// - 拷贝脚本文件
FileUtil.copy(shellOutputFilePath + ".sh", distOutputPath, true);
FileUtil.copy(shellOutputFilePath + ".bat", distOutputPath, true);
// - 拷贝源模板文件
FileUtil.copy(sourceCopyDestPath, distOutputPath, true);





 


 













a397a64c83c649e6ba3bd5cd21db43b3

3. 其他扩展思路

1. 支持Git托管项目

  • 工具生成的生成器支持使用 Git 版本控制工具来托管,可以根据元信息配置让开发者选择是否开启该特性
  • 实现思路:可以通过 Process 执行 git init 命令,并复制 Java 的 .gitignore 模板文件到生成器中

3. 健壮性优化

1. 什么是健壮性

通常是指程序在不同条件下能否稳定运行。一个健壮的程序能够在各种不同的用户输入和使用方式下,保持正常运行,并且正确处理异常情况,而不是整个程序崩溃或导致严重错误

2. 健壮性优化策略

  • 输入校验
  • 异常处理
  • 故障恢复(eg:事务)
  • 自动重试
  • 降级

对于工具项目,影响代码生成结果的、也是需要用户修改的核心内容是元信息配置文件,所以一定要对元信息进行校验、并且用默认值来填充空值,防止用户错误输入导致的异常,从而提高健壮性

3. 元信息校验和默认值填充

1. 规则梳理

先梳理元信息中每个字段的校验规则和默认值填充规则

字段默认值校验规则
namemy-generator
description模板代码生成器
basePackagecom.listao
version1.0
authorlistao
createTime当前日期
fileConfig
—— sourceRootPath必填
—— inputRootPath.source + sourceRootPath 的最后一个层级路径
—— outputRootPath当前路径下的 generated
—— typedir
—— files.inputPath必填
—— files.outputPath等于 inputPath
—— files.typeinputPath 有文件后缀(.java)为 file,否则为 dir
—— files.generateType如果文件结尾不为 .ftl,generateType 默认为 static,否则为 dynamic
modelConfig
—— models.fieldName必填
—— models.description
—— models.typeString
—— models.defaultValue
—— models.abbr

2. 自定义异常类

元信息校验是一个很重要的操作,所以专门定义一个元信息异常类

public class MetaException extends RuntimeException {

    public MetaException(String message) {
        super(message);
    }

    public MetaException(String message, Throwable cause) {
        super(message, cause);
    }
}

3. 编写校验类

  • 要校验的字段很多、规则很多,所以新建一个独立的校验类,而不是把校验和填充默认值的代码混在初始化 Meta 对象的方法中
  • 校验和填充默认值操作都要遍历元信息,所以直接用一个类同时完成这两个操作,简化开发
package com.listao.maker.meta;

import java.io.File;
import java.nio.file.Paths;
import java.util.List;

/**
 * 元信息校验
 */
public class MetaValidator {

    public static void doValidAndFill(Meta meta) {
        // 基础信息校验和默认值
        String name = meta.getName();
        if (StrUtil.isBlank(name)) {
            name = "my-generator";
            meta.setName(name);
        }

        String description = meta.getDescription();
        if (StrUtil.isEmpty(description)) {
            description = "我的模板代码生成器";
            meta.setDescription(description);
        }

        String author = meta.getAuthor();
        if (StrUtil.isEmpty(author)) {
            author = "yupi";
            meta.setAuthor(author);
        }

        String basePackage = meta.getBasePackage();
        if (StrUtil.isBlank(basePackage)) {
            basePackage = "com.yupi";
            meta.setBasePackage(basePackage);
        }

        String version = meta.getVersion();
        if (StrUtil.isEmpty(version)) {
            version = "1.0";
            meta.setVersion(version);
        }

        String createTime = meta.getCreateTime();
        if (StrUtil.isEmpty(createTime)) {
            createTime = DateUtil.now();
            meta.setCreateTime(createTime);
        }

        // fileConfig 校验和默认值
        Meta.FileConfig fileConfig = meta.getFileConfig();
        if (fileConfig != null) {
            // sourceRootPath:必填
            String sourceRootPath = fileConfig.getSourceRootPath();
            if (StrUtil.isBlank(sourceRootPath)) {
                throw new MetaException("未填写 sourceRootPath");
            }
            // inputRootPath:.source + sourceRootPath 的最后一个层级路径
            String inputRootPath = fileConfig.getInputRootPath();
            String defaultInputRootPath = ".source" + File.separator + FileUtil.getLastPathEle(Paths.get(sourceRootPath)).getFileName().toString();
            if (StrUtil.isEmpty(inputRootPath)) {
                fileConfig.setInputRootPath(defaultInputRootPath);
            }
            // outputRootPath:默认为当前路径下的 generated
            String outputRootPath = fileConfig.getOutputRootPath();
            String defaultOutputRootPath = "generated";
            if (StrUtil.isEmpty(outputRootPath)) {
                fileConfig.setOutputRootPath(defaultOutputRootPath);
            }
            String fileConfigType = fileConfig.getType();
            String defaultType = "dir";
            if (StrUtil.isEmpty(fileConfigType)) {
                fileConfig.setType(defaultType);
            }

            // fileInfo 默认值
            List<Meta.FileConfig.FileInfo> fileInfoList = fileConfig.getFiles();
            if (CollectionUtil.isNotEmpty(fileInfoList)) {
                for (Meta.FileConfig.FileInfo fileInfo : fileInfoList) {
                    // inputPath: 必填
                    String inputPath = fileInfo.getInputPath();
                    if (StrUtil.isBlank(inputPath)) {
                        throw new MetaException("未填写 inputPath");
                    }

                    // outputPath: 默认等于 inputPath
                    String outputPath = fileInfo.getOutputPath();
                    if (StrUtil.isEmpty(outputPath)) {
                        fileInfo.setOutputPath(inputPath);
                    }
                    // type:默认 inputPath 有文件后缀(如 .java)为 file,否则为 dir
                    String type = fileInfo.getType();
                    if (StrUtil.isBlank(type)) {
                        // 无文件后缀
                        if (StrUtil.isBlank(FileUtil.getSuffix(inputPath))) {
                            fileInfo.setType("dir");
                        } else {
                            fileInfo.setType("file");
                        }
                    }
                    // generateType:如果文件结尾不为 Ftl,generateType 默认为 static,否则为 dynamic
                    String generateType = fileInfo.getGenerateType();
                    if (StrUtil.isBlank(generateType)) {
                        // 为动态模板
                        if (inputPath.endsWith(".ftl")) {
                            fileInfo.setGenerateType("dynamic");
                        } else {
                            fileInfo.setGenerateType("static");
                        }
                    }
                }
            }
        }

        // modelConfig 校验和默认值
        Meta.ModelConfig modelConfig = meta.getModelConfig();
        if (modelConfig != null) {
            List<Meta.ModelConfig.ModelInfo> modelInfoList = modelConfig.getModels();
            if (CollectionUtil.isNotEmpty(modelInfoList)) {
                for (Meta.ModelConfig.ModelInfo modelInfo : modelInfoList) {
                    // 输出路径默认值
                    String fieldName = modelInfo.getFieldName();
                    if (StrUtil.isBlank(fieldName)) {
                        throw new MetaException("未填写 fieldName");
                    }

                    String modelInfoType = modelInfo.getType();
                    if (StrUtil.isEmpty(modelInfoType)) {
                        modelInfo.setType("String");
                    }
                }
            }
        }

    }

}


















































 

























 






































 



 

















4. 圈复杂度优化

圈复杂度(Cyclomatic Complexity),是一种通过计算图中的节点、边和连接组件的数量来度量程序复杂性的指标,一般情况下,代码的分支判断越多,圈复杂度越高。一般情况下,代码圈复杂度建议 <= 10,不建议超过 20

  • 上面的代码虽然能够运行,但是过于复杂了,所有的校验规则全写在一起,会导致圈复杂度过高
  • 在 IDEA 中,可以通过 MetricsReloaded 插件来检测代码圈复杂度
b39ce0b66108446198ae9aeb0ecc4df2

找到需要检测圈复杂度的类或方法(此处是 MetaValidator),点击右键 => 分析 => 计算圈复杂度

efaf23265a8f474e9cf7295ddfbfce48

发现圈复杂度过高:

eae0435bc97d4be983d67f567fd0c0bf

在圈复杂度的计算中,通常的指标有:

  1. COGC(Cyclomatic Complexity):通常用于衡量程序中的决策点数量
  2. VG(节点个数):表示图中节点的数量
  3. EDGES(边的数量):表示图中边的数量
  4. EVG(Essential VG):表示程序中的基本节点数,是计算中剔除掉虚拟节点后的节点数量
  5. IVG(Inessential VG):表示程序中的非基本节点数,是计算中保留的虚拟节点数量

如何优化圈复杂度呢?最简单的方式就是把一段代码拆分成多个方法

  1. 抽取方法
    • 按照元信息配置的层级,将整段代码抽为 3 个方法:基础元信息校验、fileConfig 校验、modelConfig 校验
    • 在 IDEA 中可以使用 Refactor 重构能力快速抽取
333f07dde82749b7818f49f3c5c0d02f
  1. 在抽取的方法中使用 卫语句 ,尽早返回
    • 卫语句是在进入主要逻辑之前添加的条件检查语句,以确保程序在执行主要逻辑之前满足某些前提条件,这种技术有助于提高代码的可读性和可维护性
    • 在 IDEA 中可以在 if 代码行上按 Alt + Enter 键,使用 Invert If 选项快速反转 if 条件
8579aac7093c491f9402da7a4c04cd18
  1. 使用工具类减少判断代码
// Hutool 的 `StrUtil.blankToDefault` 代替 `if (StrUtil.isBlank(xxx))`
String name = StrUtil.blankToDefault(meta.getName(), "my-generator");
  1. 再次检测圈复杂度,发现降低了很多倍
6c79d2ae56424f599e03e22c97b047fc
package com.listao.maker.meta;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.listao.maker.meta.enums.FileGenerateTypeEnum;
import com.listao.maker.meta.enums.FileTypeEnum;
import com.listao.maker.meta.enums.ModelTypeEnum;

import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 元信息校验
 */
public class MetaValidator {

    public static void doValidAndFill(Meta meta) {
        validAndFillMetaRoot(meta);
        validAndFillFileConfig(meta);
        validAndFillModelConfig(meta);
    }

    public static void validAndFillModelConfig(Meta meta) {
        Meta.ModelConfig modelConfig = meta.getModelConfig();
        if (modelConfig == null) {
            return;
        }
        // modelConfig 默认值
        List<Meta.ModelConfig.ModelInfo> modelInfoList = modelConfig.getModels();
        if (!CollectionUtil.isNotEmpty(modelInfoList)) {
            return;
        }
        for (Meta.ModelConfig.ModelInfo modelInfo : modelInfoList) {
            // 为 group,不校验
            String groupKey = modelInfo.getGroupKey();
            if (StrUtil.isNotEmpty(groupKey)) {
                // 生成中间参数
                List<Meta.ModelConfig.ModelInfo> subModelInfoList = modelInfo.getModels();
                String allArgsStr = modelInfo.getModels().stream()
                        .map(subModelInfo -> String.format("\"--%s\"", subModelInfo.getFieldName()))
                        .collect(Collectors.joining(", "));
                modelInfo.setAllArgsStr(allArgsStr);
                continue;
            }

            // 输出路径默认值
            String fieldName = modelInfo.getFieldName();
            if (StrUtil.isBlank(fieldName)) {
                throw new MetaException("未填写 fieldName");
            }

            String modelInfoType = modelInfo.getType();
            if (StrUtil.isEmpty(modelInfoType)) {
                modelInfo.setType(ModelTypeEnum.STRING.getValue());
            }
        }
    }

    public static void validAndFillFileConfig(Meta meta) {
        // fileConfig 默认值
        Meta.FileConfig fileConfig = meta.getFileConfig();
        if (fileConfig == null) {
            return;
        }
        // sourceRootPath:必填
        String sourceRootPath = fileConfig.getSourceRootPath();
        if (StrUtil.isBlank(sourceRootPath)) {
            throw new MetaException("未填写 sourceRootPath");
        }
        // inputRootPath:.source + sourceRootPath 的最后一个层级路径
        String inputRootPath = fileConfig.getInputRootPath();
        String defaultInputRootPath = ".source/" + FileUtil.getLastPathEle(Paths.get(sourceRootPath)).getFileName().toString();
        if (StrUtil.isEmpty(inputRootPath)) {
            fileConfig.setInputRootPath(defaultInputRootPath);
        }
        // outputRootPath:默认为当前路径下的 generated
        String outputRootPath = fileConfig.getOutputRootPath();
        String defaultOutputRootPath = "generated";
        if (StrUtil.isEmpty(outputRootPath)) {
            fileConfig.setOutputRootPath(defaultOutputRootPath);
        }
        String fileConfigType = fileConfig.getType();
        String defaultType = FileTypeEnum.DIR.getValue();
        if (StrUtil.isEmpty(fileConfigType)) {
            fileConfig.setType(defaultType);
        }

        // fileInfo 默认值
        List<Meta.FileConfig.FileInfo> fileInfoList = fileConfig.getFiles();
        if (!CollectionUtil.isNotEmpty(fileInfoList)) {
            return;
        }
        for (Meta.FileConfig.FileInfo fileInfo : fileInfoList) {
            String type = fileInfo.getType();
            // 类型为 group,不校验
            if (FileTypeEnum.GROUP.getValue().equals(type)) {
                continue;
            }
            // inputPath: 必填
            String inputPath = fileInfo.getInputPath();
            if (StrUtil.isBlank(inputPath)) {
                throw new MetaException("未填写 inputPath");
            }

            // outputPath: 默认等于 inputPath
            String outputPath = fileInfo.getOutputPath();
            if (StrUtil.isEmpty(outputPath)) {
                fileInfo.setOutputPath(inputPath);
            }
            // type:默认 inputPath 有文件后缀(如 .java)为 file,否则为 dir
            if (StrUtil.isBlank(type)) {
                // 无文件后缀
                if (StrUtil.isBlank(FileUtil.getSuffix(inputPath))) {
                    fileInfo.setType(FileTypeEnum.DIR.getValue());
                } else {
                    fileInfo.setType(FileTypeEnum.FILE.getValue());
                }
            }
            // generateType:如果文件结尾不为 Ftl,generateType 默认为 static,否则为 dynamic
            String generateType = fileInfo.getGenerateType();
            if (StrUtil.isBlank(generateType)) {
                // 为动态模板
                if (inputPath.endsWith(".ftl")) {
                    fileInfo.setGenerateType(FileGenerateTypeEnum.DYNAMIC.getValue());
                } else {
                    fileInfo.setGenerateType(FileGenerateTypeEnum.STATIC.getValue());
                }
            }
        }
    }

    public static void validAndFillMetaRoot(Meta meta) {
        // 校验并填充默认值
        String name = StrUtil.blankToDefault(meta.getName(), "my-generator");
        String description = StrUtil.emptyToDefault(meta.getDescription(), "我的模板代码生成器");
        String author = StrUtil.emptyToDefault(meta.getAuthor(), "listao");
        String basePackage = StrUtil.blankToDefault(meta.getBasePackage(), "com.listao");
        String version = StrUtil.emptyToDefault(meta.getVersion(), "1.0");
        String createTime = StrUtil.emptyToDefault(meta.getCreateTime(), DateUtil.now());
        meta.setName(name);
        meta.setDescription(description);
        meta.setAuthor(author);
        meta.setBasePackage(basePackage);
        meta.setVersion(version);
        meta.setCreateTime(createTime);
    }
}

























 



































 








































































 















然后在初始化 Meta 对象时,调用校验方法。修改后的 MetaManager

private static Meta initMeta() {
    String metaJson = ResourceUtil.readUtf8Str("meta.json");
    Meta newMeta = JSONUtil.toBean(metaJson, Meta.class);
    // 校验和处理默认值
    MetaValidator.doValidAndFill(newMeta);
    return newMeta;
}




 


4. 可扩展性优化

1. 什么是可扩展性

可扩展性:指程序在不修改结构或代码的情况下,能够灵活地添加新的功能,并适应新的需求和项目变化

  • 可扩展性又有很多细分。开发人员,最常关注的还是功能可扩展性
    • 功能可扩展性
    • 性能可扩展性
    • 资源可扩展性

2. 枚举值定义

元信息中的字段定义枚举值,来替代程序中的硬编码,使得代码更规范、更好理解、更利于维护和扩展


  1. 文件类型枚举
public enum FileTypeEnum {

    DIR("目录", "dir"),
    FILE("文件", "file");

    private final String text;

    private final String value;

    FileTypeEnum(String text, String value) {
        this.text = text;
        this.value = value;
    }

    public String getText() {
        return text;
    }

    public String getValue() {
        return value;
    }
}
  1. 文件生成类型枚举
/**
 * 文件生成类型枚举
 */
public enum FileGenerateTypeEnum {

    DYNAMIC("动态", "dynamic"),
    STATIC("静态", "static");

    private final String text;

    private final String value;

    FileGenerateTypeEnum(String text, String value) {
        this.text = text;
        this.value = value;
    }

    public String getText() {
        return text;
    }

    public String getValue() {
        return value;
    }
}
  1. 模型类型枚举
/**
 * 模型类型枚举
 */
public enum ModelTypeEnum {

    STRING("字符串", "String"),
    BOOLEAN("布尔", "boolean");

    private final String text;

    private final String value;

    ModelTypeEnum(String text, String value) {
        this.text = text;
        this.value = value;
    }

    public String getText() {
        return text;
    }

    public String getValue() {
        return value;
    }
}

有了枚举值后,就可以把 MetaValidator 中的默认值由硬编码字符串替换为读取枚举值的 value

3. 模板方法模式

MainGenerator 作用:读取元信息,然后根据流程生成不同的代码或执行不同的操作

1. 什么是模板方法

通过父类定义一套算法的标准执行流程,然后由子类具体实现每个流程的操作。使得子类在不改变执行流程结构的情况下,可以自主定义某些步骤的实现

2. GenerateTemplate

  1. 流程梳理
    • 对已有 MainGenerator 程序的流程进行梳理,包括:复制原始文件、代码生成、构建 jar 包、封装脚本、生成精简版的程序
  2. 新建父类,定义生成器程序的流程
  3. 方法抽取
    • 注意要确保每个方法的作用域为 protected,而且不能为 static,使得这些方法能被子类重写
package com.listao.maker.generator.main;

public abstract class GenerateTemplate {

    public static void main(String[] args) throws TemplateException, IOException, InterruptedException {
        // GenerateTemplate generateTemplate = new MainGenerator();
        GenerateTemplate generateTemplate = new ZipGenerator();
        generateTemplate.doGenerate();
    }

    public void doGenerate() throws TemplateException, IOException, InterruptedException {
        Meta meta = MetaManager.getMetaObject();
        String projectPath = System.getProperty("user.dir");
        String outputPath = projectPath + File.separator + ".tmp" + File.separator + meta.getName();
        doGenerate(meta, outputPath);
    }

    /**
     * 生成
     */
    public void doGenerate(Meta meta, String outputPath) throws TemplateException, IOException, InterruptedException {
        if (!FileUtil.exist(outputPath)) {
            FileUtil.mkdir(outputPath);
        }

        // 1. 复制原始文件
        String sourceCopyDestPath = copySource(meta, outputPath);

        // 2. 代码生成
        generateCode(meta, outputPath);

        // 3. 构建 jar 包
        String jarPath = buildJar(meta, outputPath);

        // 4. 封装脚本
        String shellOutputFilePath = buildScript(outputPath, jarPath);

        // 5. 生成精简版的程序(产物包)
        buildDist(outputPath, sourceCopyDestPath, jarPath, shellOutputFilePath);
    }


    /**
     * 复制原始文件
     */
    protected String copySource(Meta meta, String outputPath) {
        String sourceRootPath = meta.getFileConfig().getSourceRootPath();
        String sourceCopyDestPath = outputPath + File.separator + ".source";
        FileUtil.copy(sourceRootPath, sourceCopyDestPath, false);
        return sourceCopyDestPath;
    }

    /**
     * 代码生成
     */
    protected void generateCode(Meta meta, String outputPath) throws IOException, TemplateException {
        // 读取 resources 目录
        ClassPathResource classPathResource = new ClassPathResource("");
        String inputResourcePath = classPathResource.getAbsolutePath();
        // String inputResourcePath = "";

        // 3. Java 包基础路径
        String outputBasePackage = meta.getBasePackage();
        String outputBasePackagePath = StrUtil.join("/", StrUtil.split(outputBasePackage, "."));
        String outputBaseJavaPackagePath = outputPath + File.separator + "src/main/java/" + outputBasePackagePath;

        String inputFilePath;
        String outputFilePath;

        // 4. model.DataModel
        inputFilePath = inputResourcePath + File.separator + "templates/java/model/DataModel.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/model/DataModel.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 5. cli.command.ConfigCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/ConfigCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/ConfigCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // cli.command.GenerateCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/GenerateCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/GenerateCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // cli.command.ListCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/ListCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/ListCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // cli.command.JsonGenerateCommand
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/command/JsonGenerateCommand.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/command/JsonGenerateCommand.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // cli.CommandExecutor
        inputFilePath = inputResourcePath + File.separator + "templates/java/cli/CommandExecutor.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/cli/CommandExecutor.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // Main
        inputFilePath = inputResourcePath + File.separator + "templates/java/Main.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/Main.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 6.
        // generator.DynamicGenerator
        inputFilePath = inputResourcePath + File.separator + "templates/java/generator/DynamicGenerator.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/generator/DynamicGenerator.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // generator.MainGenerator
        inputFilePath = inputResourcePath + File.separator + "templates/java/generator/MainGenerator.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/generator/MainGenerator.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // generator.StaticGenerator
        inputFilePath = inputResourcePath + File.separator + "templates/java/generator/StaticGenerator.java.ftl";
        outputFilePath = outputBaseJavaPackagePath + "/generator/StaticGenerator.java";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 7. pom.xml
        inputFilePath = inputResourcePath + File.separator + "templates/pom.xml.ftl";
        outputFilePath = outputPath + File.separator + "pom.xml";
        DynamicFileGenerator.doGenerate(inputFilePath, outputFilePath, meta);

        // 8. README.md
        inputFilePath = inputResourcePath + File.separator + "templates/README.md.ftl";
        outputFilePath = outputPath + File.separator + "README.md";
        DynamicFileGenerator.doGenerate(inputFilePath , outputFilePath, meta);
    }

    /**
     * 构建 jar 包
     *
     * @return 返回 jar 包的相对路径
     */
    protected String buildJar(Meta meta, String outputPath) throws IOException, InterruptedException {
        JarGenerator.doGenerate(outputPath);
        String jarName = String.format("%s-%s-jar-with-dependencies.jar", meta.getName(), meta.getVersion());
        String jarPath = "target/" + jarName;
        System.out.println("jarPath = " + jarPath);
        return jarPath;
    }

    /**
     * 封装脚本
     */
    protected String buildScript(String outputPath, String jarPath) throws IOException {
        String shellOutputFilePath = outputPath + File.separator + "generator";
        ScriptGenerator.doGenerate(shellOutputFilePath, jarPath);
        return shellOutputFilePath;
    }

    /**
     * 生成精简版程序
     *
     * @return 产物包路径
     */
    protected String buildDist(String outputPath, String sourceCopyDestPath, String jarPath, String shellOutputFilePath) {
        String distOutputPath = outputPath + "-dist";
        // 拷贝 jar 包
        String targetAbsolutePath = distOutputPath + File.separator + "target";
        FileUtil.mkdir(targetAbsolutePath);
        String jarAbsolutePath = outputPath + File.separator + jarPath;
        FileUtil.copy(jarAbsolutePath, targetAbsolutePath, true);
        // 拷贝脚本文件
        FileUtil.copy(shellOutputFilePath + ".sh", distOutputPath, true);
        FileUtil.copy(shellOutputFilePath + ".bat", distOutputPath, true);
        // 拷贝源模板文件
        FileUtil.copy(sourceCopyDestPath, distOutputPath, true);
        return distOutputPath;
    }

    /**
     * 制作压缩包
     *
     * @return 压缩包路径
     */
    protected String buildZip(String outputPath) {
        String zipPath = outputPath + ".zip";
        ZipUtil.zip(outputPath, zipPath);
        return zipPath;
    }

}










 









 
























 









 
















































































 










 










 



















 






3. MainGenerator

编写模板方法的具体实现子类

  • 覆写 buildDist 方法,不再生成精简版的程序
  • 覆写 buildDist 方法,进行压缩
/**
 * 生成代码生成器
 */
public class MainGenerator extends GenerateTemplate {

    @Override
    protected String buildDist(String outputPath, String sourceCopyDestPath, String jarPath, String shellOutputFilePath) {
        System.out.println("不要给我输出 dist 啦!");
        return "";
    }

}

4. ZipGenerator

/**
 * 生成代码生成器压缩包
 */
public class ZipGenerator extends GenerateTemplate {

    @Override
    protected String buildDist(String outputPath, String sourceCopyDestPath, String jarPath, String shellOutputFilePath) {
        String distPath = super.buildDist(outputPath, sourceCopyDestPath, jarPath, shellOutputFilePath);
        System.out.println("distPath = " + distPath);
        return super.buildZip(distPath);
    }
}