编程随笔 编程随笔
  • 前端
  • 后端
  • 嵌入式
  • 星球项目
  • 开源项目
  • 海康AGV
  • 四向车
  • 工具类
  • 项目仓库

    • 部署仓库 (opens new window)
    • 代码仓库 (opens new window)
  • vuepress插件

    • 自动生成导航栏与侧边栏 (opens new window)
    • 评论系统 (opens new window)
    • 全文搜索 (opens new window)
    • 选项卡 (opens new window)
    • 自动生成sitemap (opens new window)
  • 自主开发插件

    • 批量操作frontmatter (opens new window)
    • 链接美化 (opens new window)
    • 折叠代码块 (opens new window)
    • 复制代码块 (opens new window)

liyao52033

走运时,要想到倒霉,不要得意得过了头;倒霉时,要想到走运,不必垂头丧气。心态始终保持平衡,情绪始终保持稳定,此亦长寿之道
  • 前端
  • 后端
  • 嵌入式
  • 星球项目
  • 开源项目
  • 海康AGV
  • 四向车
  • 工具类
  • 项目仓库

    • 部署仓库 (opens new window)
    • 代码仓库 (opens new window)
  • vuepress插件

    • 自动生成导航栏与侧边栏 (opens new window)
    • 评论系统 (opens new window)
    • 全文搜索 (opens new window)
    • 选项卡 (opens new window)
    • 自动生成sitemap (opens new window)
  • 自主开发插件

    • 批量操作frontmatter (opens new window)
    • 链接美化 (opens new window)
    • 折叠代码块 (opens new window)
    • 复制代码块 (opens new window)
  • 代码生成器

    • 开发本地代码生成器
      • 1 项目的初始化
      • 2 静态文件生成
      • 3 动态文件代码生成
      • 4 动静结合 - 生成完整代码
    • 命令行开发
    • 模板生成工具
  • 项目实战
  • 代码生成器
华总
2023-11-19
0
0
目录

开发本地代码生成器原创

# 1 项目的初始化

# 1、初始化根目录

由于我们的项目包含多个阶段,本质上是多个项目,所以为了统一管理整个项目,我们创建一个干净的 yuzi-generator 空文件夹,作为整个项目的根目录,后续各阶段的项目和目录都放到它之下。

这样做还有一个好处,就是让不同项目模块可以用 相对路径 寻找文件,便于整个项目的开源共享。

建议大家养成习惯,使用 Git 来管理项目。如果使用 IDEA 开发工具来创建新项目,可以直接勾选 Create Git repository ,工具会自动帮你初始化项目为 Git 仓库。

如下图:

image.png

当然,也可以进入项目根目录,执行 git init 命令创建 Git 仓库。

# 2、忽略无用提交

创建好新项目后,使用 IDEA 开发工具打开项目,进入底部的 Git 标签,会发现很多和项目无关的 IDEA 自动生成的工程文件被添加到了 Git 托管。

image.png

但我们是不希望提交这些文件的,没有意义,所以需要使用 .gitignore 文件来忽略这些文件,不让它们被 Git 托管。

如何编写 .gitignore 文件呢?

其实很简单,不用自己编写!我们在 IDEA 的 Settings => Plugins 中搜索 .ignore 插件并安装:

image.png

然后在项目根目录处选中右键,使用 .ignore 插件创建 .gitignore 文件:

image.png

.ignore 插件提供了很多默认的 .gitignore 模板,根据自己的项目类型和使用的开发工具进行选择,此处我们选择 Java 和 JetBrains 模板:

image.png

然后可以在项目根目录看到生成的 .gitignore 文件,模板已经包含了常用的 Java 项目忽略清单,比如编译后的文件、日志文件、压缩包等:

image.png

让我们再手动添加几个要忽略的目录和文件,比如打包生成的 target 目录:

image.png

但是,我们会发现,即使有些文件已经添加到了 .gitignore 文件中,在 IDEA 中显示的还是绿色(已被 Git 托管)状态。如下图:

image.png

这是因为这些文件已经被 Git 跟踪。而 .gitignore 文件仅影响未跟踪的文件,如果文件已经被 Git 跟踪,那么 .gitignore 文件对它们没有影响。

所以我们需要打开终端,在项目根目录下执行如下命令,取消 Git 跟踪:

执行效果如图:

image.png

可以看到文件变成了红色(未被 Git 托管)或黄色(被忽略)状态:

image.png

然后,让我们将 .gitignore 文件添加到 Git 暂存区,让它能够被 Git 管理。

image.png

项目根目录就初始化完成了,在项目根目录中新建一个 README.md 文件,用于介绍项目、记录自己的学习和开发过程等

# 2 静态文件生成

# 1、使用Hutool (opens new window)

优点:方便快捷,一行代码即可wanc

缺点:只能复制整个文件夹,不能随意复制

现在我们想复制目录下的所有文件,可以直接使用 Hutool 的 copy 方法,方法信息如下图,一定要格外注意输入参数的含义

image.png

先编写一个公开的静态方法 copyFilesByHutool,方法中的核心代码就一行,直接调用 Hutool 提供的 FileUtil.copy 方法,就能实现指定目录下所有文件的复制!

    /**
     * 拷贝文件(Hutool 实现,会将输入目录完整拷贝到输出目录下)
     * @param inputPath  输入
     * @param outputPath 输出
     */
    public static void copyFileByHutool(String inputPath, String outputPath) {
        FileUtil.copy(inputPath, outputPath, false);
    }
1
2
3
4
5
6
7
8

然后编写一个 Main 方法来调用这个方法即可,完整复制我们之前准备好的 ACM 示例代码模板(建议使用相对路径)。 示例代码如下:

 public static void main(String[] args) {
        // 获取整个项目的根路径
        String projectPath = System.getProperty("user.dir");
        File parentFile = new File(projectPath).getParentFile();
        // 输入路径:ACM 示例代码模板目录
        String inputPath = new File(parentFile, "yuzi-generator-demo-projects/acm-template").getAbsolutePath();
        // 输出路径:直接输出到项目的根目录
        copyFileByHutool(inputPath, projectPath);
    }
1
2
3
4
5
6
7
8
9

注意,上述代码中,我们通过 System.getProperty("user.dir") 获取到的路径是否是根路径,执行后就能看到项目目录下成功复制了完整的目录

# 2、递归遍历文件

优点:可随意复制任何想要的文件,不限制

缺点:需要自己实现,比价麻烦,可能还会有点小bug

# 文件操作 API

1)拷贝文件:

Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[0]));
1

2)创建多级文件夹(哪怕中间有目录不存在):

File dest;
dest.mkdirs()
1
2

3)判断是否为目录:

File dest;
dest.isDirectory()
1
2

4)文件是否存在:

File dest;
dest.exists()
1
2

掌握这些 API,就能完成检测目录、创建目录、复制文件的操作了。

# 示例代码

递归算法的实现还是有一定复杂度的。核心思路就是先在目标位置创建和源项目相同的目录,然后依次遍历源目录下的所有子文件并复制;如果子文件又是一个目录,则再遍历子文件下的所有 “孙” 文件,如此循环往复。

/**
     * 递归拷贝文件(递归实现,会将输入目录完整拷贝到输出目录下)
     * @param inputPath 输入
     * @param outputPath 输出
     */
    public static void copyFilesByRecursive(String inputPath, String outputPath) {
        File inputFile = new File(inputPath);
        File outputFile = new File(outputPath);
        try {
            copyFileByRecursive(inputFile, outputFile);
        } catch (Exception e) {
            System.err.println("文件复制失败");
        }
    }

    /**
     * 文件 A => 目录 B,则文件 A 放在目录 B 下
     * 文件 A => 文件 B,则文件 A 覆盖文件 B
     * 目录 A => 目录 B,则目录 A 放在目录 B 下
     * <p>
     * 核心思路:先创建目录,然后遍历目录内的文件,依次复制
     * @param inputFile 输入文件
     * @param outputFile 输出文件
     */
   private static void copyFileByRecursive(File inputFile, File outputFile) throws IOException {
        // 判断inputFile是否为目录
        if (inputFile.isDirectory()) {
            // 创建outputFile的子目录
            File destOutputFile = new File(outputFile, inputFile.getName());
            // 如果子目录不存在,则创建
            if(!destOutputFile.exists()) {
                destOutputFile.mkdirs();
            }
            // 获取目录下的所有文件和子目录
            File[] files = inputFile.listFiles();
            // 没有子文件时,直接结束
            if (ArrayUtil.isEmpty(files)) {
                return;
            }
            // 递归调用,复制子文件
            for(File file: files){
                copyFileByRecursive(file,  destOutputFile);
            }

        } else {
            // 复制文件
            Path destPath = outputFile.toPath().resolve(inputFile.toPath().getFileName());
            Files.copy(inputFile.toPath(), destPath, StandardCopyOption.REPLACE_EXISTING);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 3 动态文件代码生成

核心步骤: 1 定义数据模型 2 编写动态模板 3 组合生成 4 完善优化

# 1、定义数据模型

针对上述需求,我们在 com.yupi.model 包下新建一个模板配置对象,用来接收要传递给模板的参数。 注意要根据替换需求选择参数的类型,比如可选生成用 boolean、字符串替换用 String,示例代码如下:

/**
 * 动态模版配置
 */
@Data
public class MainTemplateConfig {

    /**
     * 是否生成循环
     */
    private boolean loop;

    /**
     * 作者注释
     */
    private String author;

    /**
     * 输出信息
     */
    private String outputText;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2、编写动态模板

在 resources/templates 目录下新建 FTL 模板文件 MainTemplate.ftl(模板和上面定义的数据模型名称保持一致)。制作模板的方法很简单:先复制原始代码,再挖坑。 完整动态模板代码如下:

/**
* ACM 输入模板(多数之和)
* @author ${author}
*/
public class MainTemplate {
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    <#if loop>
      while (scanner.hasNext()) {
    </#if>
    // 读取输入元素个数
    int n = scanner.nextInt();

    // 读取数组
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) {
    arr[i] = scanner.nextInt();
    }

    // 处理问题逻辑,根据需要进行输出
    // 示例:计算数组元素的和
    int sum = 0;
    for (int num : arr) {
    sum += num;
    }

    System.out.println("${outputText}" + sum);
    <#if loop>
      }
    </#if>
    scanner.close();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

其中,我们使用插值表达式 ${author} 接受作者名称,使用 <#if loop> ... </#if> 分支来控制是否生成循环代码,使用 ${outputText} 控制输出信息。

# 3、组合生成

在 Main 方法中编写生成逻辑,依次完成:创建 Configuration 对象、模板对象、创建数据模型、指定输出路径、执行生成。

public class DynamicGenerator {
  
    public static void main(String[] args) throws IOException, TemplateException, freemarker.template.TemplateException {
        String projectPath = System.getProperty("user.dir");
        String inputPath = projectPath + File.separator +"/src/main/resources/templates/MainTemplate.ftl";
        String outputPath = projectPath + File.separator + "src/main/resources/templates/";
        MainTemplateConfig mainTemplateConfig = new MainTemplateConfig();
        mainTemplateConfig.setAuthor("liyao");
        mainTemplateConfig.setLoop(false);
        mainTemplateConfig.setOutputText("总和: ");
        doGenerate(inputPath, outputPath, mainTemplateConfig);
    }

    public static void doGenerate(String inputPath, String outputPath, Object model) throws IOException, TemplateException, freemarker.template.TemplateException {


        // new 出 Configuration 对象,参数为 FreeMarker 版本号
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_32);

        // 指定模板文件所在的路径
        configuration.setDirectoryForTemplateLoading(new File(inputPath).getParentFile());

        // 设置模板文件使用的字符集
        configuration.setDefaultEncoding("utf-8");

        // 创建模板对象,加载指定模板
        Template template = configuration.getTemplate("MainTemplate.ftl");

        // 创建数据模型
        MainTemplateConfig mainTemplateConfig = new MainTemplateConfig();
        mainTemplateConfig.setAuthor("yupi");
        // 不使用循环
        mainTemplateConfig.setLoop(false);
        mainTemplateConfig.setOutputText("求和结果:");

        // 生成
        Writer out =
                new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(new File(outputPath + "MainTemplate.java").toPath()),
                        StandardCharsets.UTF_8));
        template.process(model, out);

        // 生成文件后别忘了关闭哦
        out.close();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 4、完善优化

经过测试发现,如果数据模型的字符串变量不设置任何值,那么会报如下错误

image.png

所以建议给所有字符串指定一个默认值,这里有两种方法:

1)方法 1,直接给 POJO 设置默认值

private String outputText = "sum = ";
1

2)方法 2,使用 FreeMarker 的默认值操作符

System.out.println("${outputText!'sum = '}" + sum);
1

# 4 动静结合 - 生成完整代码

编写一个类,组合调用这两个生成器,先复制静态文件、再动态生成文件来覆盖即可。

public class MainGenerator {

    public static void doGenerate(Object model) throws TemplateException, IOException {

        String projectPath = System.getProperty("user.dir"); // 获取当前目录的路径
        File parentFile = new File(projectPath).getParentFile(); // 获取当前目录上一级目录的路径

        //生成静态文件
        String inputStaticPath = new File(parentFile, "yuzi-generator-demo-projects/").getAbsolutePath();
        String outputStaticPath = projectPath + File.separator + "acm-template/";
        StaticGenerator.copyFilesByRecursive(inputStaticPath, outputStaticPath);

        //生成动态文件
        String inputPath = projectPath + File.separator +"/src/main/resources/templates/MainTemplate.ftl";
        String outputPath = projectPath + File.separator + "src/main/resources/templates/";
        DynamicGenerator.doGenerate(inputPath, outputPath,  model);

    }

    /**
     * 主程序入口
     *
     * @param args 命令行参数
     * @throws TemplateException 模板异常
     * @throws IOException 输入输出异常
     */
    public static void main(String[] args) throws TemplateException, IOException {
        // 创建MainTemplateConfig对象
        MainTemplateConfig mainTemplateConfig = new MainTemplateConfig();
        // 设置作者为"liyao"
        mainTemplateConfig.setAuthor("liyao");
        // 设置循环为false
        mainTemplateConfig.setLoop(false);
        // 设置输出文本为"总和: "
        mainTemplateConfig.setOutputText("总和: ");
        // 调用doGenerate方法生成模板
        doGenerate(mainTemplateConfig);
    }



}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#星球项目#代码生成器
上次更新: 2025/02/18 14:29:44
命令行开发

命令行开发→

最近更新
01
jFlash使用 原创
03-24
02
中央仓库上传指南 原创
03-23
03
模板生成工具 原创
02-18
04
RTC实时时钟 原创
02-12
05
keil调试 原创
01-21
更多文章>
Copyright © 2023-2025 liyao52033  All Rights Reserved
备案号:鄂ICP备2023023964号-1    
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式