Foxnic-Web 代码生成 (1) —— 开始生成代码

LeeFJ
• 阅读 605
Foxnic-Web 代码生成 (1) —— 开始生成代码
基本原理

  使用 Foxnic-Web 以及 Foxnic-SQL 进行应用开发时,都可以支持代码生成。他们的区别是,基于 Foxnic-SQL 的快速 main 函数启动的应用,只需要生成 Model 和 Service 即可。基于 Foxnic-Web 开发 Web 应用时,除了生成 Model 和 Service 以外,还要生成 Proxy、Controller、UI界面等。

  Foxnic 的代码生成是基于数据表的,所以当表结构变更,甚至只是注释的调整,我们也是建议重新生成必要的代码。在 Foxnic 的体系中,我们认为最初的表结构设计、ER图设计,就是这个系统设计的起点。后续的程序设计或数据结构设计都是表结构设计的延续。

  Foxnic 的代码生成体系希望开发者可以有一个较高的开发起点,可以基于生成的代码直接开发应用,甚至是代码生成后无需修改就可以直接使用了。另一方面,我们又不关闭二次开发的开放性,毕竟自由的修改代码才是软件系统可以按需定制的终极路径。这也是 Foxnic 体系没有走无代码或低代码平台的原因。

  本文中的示例代码均可在 https://gitee.com/LeeFJ/foxnic-samples 项目中找到。

生成表结构元数据

  代码生成的第一步是生成表结构元数据。我们知道,数据表、列、索引等信息都存储在数据库内,我们在 java 程序内无法直接引用。但有些时候,我们又需要用到这些东西,特别是表名、字段等。Foxnic-Web 在代码生成、以及其它业务逻辑编写时都有可能用到这些元数据。

  所以,我们要通过一种方式将数据库元数据转换成 java 结构,这种结构是通过代码生成的。Java 生成的元数据它是静态的,不会随着变结构的改变而改变,Foxnic-Web 要求表结构变动后,需要重新生成元数据类。

  示例项目的 webfull 项目下的 WebFullDBMetaGenerator 类用于生成数据库元数据类:

package org.github.foxnic.web.generator.constants;

import com.github.foxnic.dao.spec.DAO;
import com.github.foxnic.generator.builder.constants.DBMetaClassFile;
import org.github.foxnic.web.generator.config.WebFullConfigs;

public class WebFullDBMetaGenerator {
    /**
    * 运行main函数生成代码
    * */
    public static void main(String[] args) throws Exception {
        WebFullDBMetaGenerator g = new WebFullDBMetaGenerator();
        g.buildDBMeta();
    }
    /**
    * 生成DBMeta数据
    * */
    private void buildDBMeta() {
        WebFullConfigs configs=new WebFullConfigs("webfull-service-example");
        DAO dao=configs.getDAO();
        DBMetaClassFile dbMetaBuilder=new DBMetaClassFile(dao,configs.getDomainProject(),configs.getProjectConfigs().getDomainConstantsPackage(),"WebFullTables");
        dbMetaBuilder.setTableFilter(table->{
            table=table.toLowerCase();
            // 仅生成以 example_ 开头的表
            if(table.startsWith("webfull_")) return true;
            return false;
        });
        dbMetaBuilder.save(true);
    }
}

  执行 main 函数后,在 domain 模块内生成 WebFullTables 类,后面生成其它代码,我们会用到这个类中数据库表结构定义的常量。

非 Web 环境的代码生成

  事实上,非 Web 环境的开发是很少的,但是我们在讲解 Foxnic-SQL 部分的时候,也用到了非 Web 环境的代码生成,大家参考 Foxnic-SQL 相关的文档即可。

  在 https://gitee.com/LeeFJ/foxnic-samples 项目的 com/leefj/foxnic/sql/demo/generator 目录的 ExampleCodeGenerator.java 中有示例,大家看代码结合我们的文档,很容易理解。后面的篇幅我们着重介绍 Web 环境下的代码生成,所有 Web 环境下代码生成的原理一样适用于非 Web 环境。

  为了方便理解,我们还是贴一下代码:

package com.leefj.foxnic.sql.demo.generator;

import com.github.foxnic.commons.project.maven.MavenProject;
import com.github.foxnic.dao.spec.DAO;
import com.github.foxnic.generator.builder.model.PoClassFile;
import com.github.foxnic.generator.config.ModuleContext;
import com.github.foxnic.sql.meta.DBTable;
import com.leefj.foxnic.sql.demo.app.domain.example.Address;
import com.leefj.foxnic.sql.demo.app.domain.example.Goods;
import com.leefj.foxnic.sql.demo.app.domain.example.Order;
import com.leefj.foxnic.sql.demo.app.domain.example.OrderItem;
import com.leefj.foxnic.sql.demo.config.DBInstance;
import com.leefj.foxnic.sql.demo.config.db.ExampleTables;

/**
* 代码生成器
* */
public class ExampleCodeGenerator {
    public static interface  Config  {
        void config(PoClassFile poType);
    }

    private static final String BASE_PACKAGE = "com.leefj.foxnic.sql.demo.app";

    /**
    * 需要首先运行 ExampleDBMetaGenerator 生成 ExampleTables 类
    * */
    public static void main(String[] args) {
        ExampleCodeGenerator generator = new ExampleCodeGenerator();
        // 生成商品实体类
        generator.generate(ExampleTables.EXAMPLE_GOODS.$TABLE, poType -> {
            // Goods 对象 通过 orderList 属性持有 Order
            poType.addListProperty(Goods.class,"orderList","订单明细商品","订单明细商品");
            // Goods 对象 通过 addressList 属性持有 Address
            poType.addListProperty(Address.class,"addressList","收件地址","收件地址,包括收件人以及手机号码");
            // Goods 对象 通过 itemList 属性持有 OrderItem
            poType.addListProperty(OrderItem.class,"itemList","订单明细","订单明细");
        });
        // 生成订单实体类
        generator.generate(ExampleTables.EXAMPLE_ORDER.$TABLE , poType -> {
            // Order 对象 通过 goodsList 属性持有 Goods
            poType.addListProperty(Goods.class,"goodsList","订单明细商品","订单明细商品");
            // Order 对象 通过 address 属性持有 Address
            poType.addSimpleProperty(Address.class,"address","收件地址","收件地址,包括收件人以及手机号码");
            // Order 对象 通过 itemList 属性持有 OrderItem
            poType.addListProperty(OrderItem.class,"itemList","订单明细","订单明细");
        });
        // 生成订单明细实体类
        generator.generate(ExampleTables.EXAMPLE_ORDER_ITEM.$TABLE, poType -> {
            // OrderItem 对象 通过 goodsList 属性持有 Goods
            poType.addSimpleProperty(Goods.class,"goods","订单明细商品","订单明细商品");
            // OrderItem 对象 通过 address 属性持有 Address
            poType.addSimpleProperty(Address.class,"address","收件地址","收件地址,包括收件人以及手机号码");
            // OrderItem 对象 通过 order 属性持有 Order
            poType.addListProperty(Order.class,"order","订单","订单");
        });
        // 生成地址实体类
        generator.generate(ExampleTables.EXAMPLE_ADDRESS.$TABLE, poType -> {
            // Address 对象 通过 goodsList属性 持有 Goods
            poType.addListProperty(Goods.class,"goodsList","订单明细商品","订单明细商品");
            // Address 对象 通过 orderList 持有 Order
            poType.addListProperty(Address.class,"orderList","收件地址","收件地址,包括收件人以及手机号码");
            // Address 对象 通过 itemList 持有 OrderItem
            poType.addListProperty(OrderItem.class,"itemList","订单明细","订单明细");
        });
    }
    /**
    * 按表生成
    * */
    public void generate(DBTable table) {
        generate(table,null);
    }
    /**
    * 按表生成
    * */
    public void generate(DBTable table,Config config) {
        DAO dao = DBInstance.DEFAULT.dao();
        MavenProject project = GeneratorUtil.getProject();
        String pkg = table.name().split("_")[0];
        String prefix = pkg + "_";
        ModuleContext context = new ModuleContext(GeneratorUtil.initGlobalSettings(),table,prefix,BASE_PACKAGE + "." + pkg);
        context.setDomainProject(project);
        context.setServiceProject(project);
        context.setDAO(dao);
        if(config!=null) {
            config.config(context.getPoClassFile());
        }
        context.buildPo();
        context.buildVo();
        context.buildService();
    }                    
}
Web 环境的代码生成

  Web 环境的代码生成本文以 webfull 项目为示例, 它以 WebFullCodeStarter 开始,初始化代码生成器,各模块配置,最后按生成用户指定的模块代码。代码生成的模块配置是按表配置的,一个表对应一套代码。我们先来看一下 WebFullCodeStarter 的代码:

package org.github.foxnic.web.generator.module;

import com.github.foxnic.generator.util.ModuleCodeGenerator;
import org.github.foxnic.web.generator.module.bpm.ExampleReimbursementConfig;
import org.github.foxnic.web.generator.module.mall.ExampleAddressConfig;
import org.github.foxnic.web.generator.module.mall.ExampleGoodsConfig;
import org.github.foxnic.web.generator.module.mall.ExampleOrderConfig;
import org.github.foxnic.web.generator.module.mall.ExampleOrderItemConfig;
/**
* 代码生成启动类
* */
public class WebFullCodeStarter extends ModuleCodeGenerator {

    public static void main(String[] args) {
        // 新建启动类对象
        WebFullCodeStarter g=new WebFullCodeStarter();
        // 初始化本次需要生成代码的模块
        g.initModules();
        // 启动
        g.start();
    }
    /**
    * 初始化本次需要生成代码的模块
    * */
    public void initModules() {
        initExampleModules();
        initBPMModules();
    }
    /**
    * 初始化 BPM 示例模块
    * */
    private void initBPMModules() {
        this.addConfig(new ExampleReimbursementConfig());
    }
    /**
    * 初始化订单示例模块
    * */
    private void initExampleModules() {
        this.addConfig(new ExampleGoodsConfig());
        this.addConfig(new ExampleAddressConfig());
        this.addConfig(new ExampleOrderConfig());
        this.addConfig(new ExampleOrderItemConfig());
    }
}

  启动 WebFullCodeStarter 的 main 函数,输出如下:

输入 ALL 生成全部

或输入需模块序号: (1) webfull_example_goods (2) webfull_example_address (3) webfull_example_order (4) webfull_example_order_item (5) webfull_example_reimbursement

|

  此时,输入 all 或 a 生成列表中全部模块的代码,如果输入指定模块的序号,则社能成指定模块的代码。

Foxnic-Web 代码生成 (1) —— 开始生成代码

  从这个动图中,我们不难发现,代码生成器不必重启可以反复生成。针对一些界面的生成,可以边修改代码生成的配置,边生成边测试,我们把它叫做迭代式的代码生成。

  为了能够反复进行代码生成,我们针对模块代码的结构做了一些特殊的设计,尽量把业务逻辑的代码剥离到某个单独的文件,其它文件则可以反复生成,这种代码生成的方式极大的提高了开发效率。

代码生成的结果

  某个模块的代码生成后,在项目的各个 Maven 子模块下生成相应的 Java 类、Html 文件、js 文件。下面对各个文件说明如下:

文件类型 示例 位置 说明
PO Order.java domain 与数据表对应的实体类。
POMeta OrderMeta.java domain 针对 PO 类的元数据描述,包含一个 PO Proxy 内部类。
VO OrderVO.java domain 用于 API 接口传参、返回等。
VOMeta OrderVOMeta.java domain 针对 VO 类的元数据描述,包含一个 VO Proxy 内部类。
自定义模型 CustomModel domain 代码生成时自定义的模型类。
Proxy OrderServiceProxy.java proxy 接口代理、Feign代理接口、接口路径定义。
服务接口 IOrderService.java service 服务接口
服务实现 OrderServiceImpl.java service 服务接口实现
流程支持 OrderBpmEventAdaptor.java service 可选;开启流程审批时会生成,处理流程逻辑。
API控制器 OrderController service Rest API 接口控制器
页面控制器 OrderPageController view 页面控制器
列表页 order_list.html view 列表页,表格展示数据。
列表页JS order_list.js view 列表页对应的JS文件
表单页 order_form.html view 表单页,表单方式展示、编辑数据。
表单页JS order_form.js view 表单页JS
逻辑扩展JS order_ext.js view 剥离表单、表格业务逻辑,使页面代码可反复生成。

  从上面的表格我们可以看到,一个数据表对用的简单模块,就包含可诸多文件。其中,黑色加粗的几项不建议手动修改,如要修改可以通过代码生成的方式重新生成。

  页面如果有业务逻辑,尽量到 xxx_ext.js 文件修改,因为一旦修改也列表页或表单也的代码就无法再次重新生成代码。

项目依赖

  Foxnic-Web 代码生成的方式是迭代式的,所以被生成的代码需要依赖到代码生成的项目中。以 webfull 项目的 generator 项目为例,在它的 pom 文件中需要加入 domain、proxy、service 和 view 的依赖。

<dependencies>
  <!-- 通用基础模块 -->
  <dependency>
    <groupId>com.github.foxnic.web</groupId>
    <artifactId>framework-boot</artifactId>
    <version>${foxnic.web.version}</version>
  </dependency>
  <dependency>
    <groupId>com.github.foxnic.web</groupId>
    <artifactId>framework-cloud</artifactId>
    <version>${foxnic.web.version}</version>
  </dependency>
  <dependency>
    <groupId>com.github.foxnic</groupId>
    <artifactId>foxnic-generator</artifactId>
    <version>${foxnic.version}</version>
  </dependency>
  <!-- 当前项目基础模块 -->
  <dependency>
    <groupId>com.github.foxnic.example.webfull</groupId>
    <artifactId>webfull-proxy</artifactId>
    <version>${project.version}</version>
  </dependency>
  <dependency>
    <groupId>com.github.foxnic.example.webfull</groupId>
    <artifactId>webfull-domain</artifactId>
    <version>${project.version}</version>
  </dependency>
  <dependency>
    <groupId>com.github.foxnic.example.webfull</groupId>
    <artifactId>webfull-framework</artifactId>
    <version>${project.version}</version>
  </dependency>
  <!-- 当前项目业务模块 -->
  <dependency>
    <groupId>com.github.foxnic.example.webfull</groupId>
    <artifactId>webfull-service-example</artifactId>
    <version>${project.version}</version>
  </dependency>
  <dependency>
    <groupId>com.github.foxnic.example.webfull</groupId>
    <artifactId>webfull-view-example</artifactId>
    <version>${project.version}</version>
  </dependency>
</dependencies>
小结

  本节主要介绍了在 Foxnic-SQL 和 oxnic-Web 代码生成的基本步骤,以及代码生成的文件分布,具体作用等。尤其要注意是代码生成项目的依赖问题,可能导致异常而无法正确生成代码。

  后面的章节中,我们将进一步介绍代码生成的细节。

相关项目

  https://gitee.com/LeeFJ/foxnic

  https://gitee.com/LeeFJ/foxnic-web

  https://gitee.com/lank/eam

  https://gitee.com/LeeFJ/foxnic-samples

官方文档

  http://foxnicweb.com/docs/doc.html

点赞
收藏
评论区
推荐文章
低代码开发平台 | 低代码的衍生历程、优势及未来趋势
通过简单的拖拉拽操作,而不用编写复杂的代码,实现少写代码或者不写代码,就能快速高效完成业务目标。低代码平台演进1.低代码概念低代码是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。通过可视化进行应用程序开发的方法,具有不同经验水平的开发人员可以通过图形化的用户界面,使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程序。2.低代码衍生历
LeeFJ LeeFJ
1年前
Foxnic-Web 代码生成 (9) —— 文件覆盖与扩展
之前的文档中我们曾多次提及,Foxnic的代码生成是迭代式的,当表结构变化后,需要重新生成相关的代码。例如某表增加了一个字段,那么对应的表结构元数据需要重新生成。此外,为了维护新加的字段也需要重新生成模块代码。  这个时候,如果模块代码已经被开发人员修改,默认情况下,重新生成代码会覆盖开发人员修改过的代码。为了能够反复生成大多数的模块代码Foxnic设计了一套机制,尽可将影响降到最低。
Easter79 Easter79
3年前
springboot代码自动生成
在项目开始阶段经常需要自动生成一批代码,如果使用了mybatis则可以使用mybatisplus就可以生成mybatis相关代码。不过经常项目中还有一些mvc代码需要生成,比如说前端代码、相关sql、swagger注解、权限注解等等。下面提供一个代码生成demospringboot集成vm自动生成前端代码、controller、service、myba
LeeFJ LeeFJ
1年前
Foxnic-Web 代码生成 (3) —— 配置模型
FoxnicWeb对模型体系进行了简化,默认创建PO和VO类,且VO继承自PO。其它代码基于PO和VO实现。当然开发者也可以按需自定义模型,但自定义模型并不建议手动创建,而是通过代码生成工具进行创建。  代码生成配置类的configModel方法将全部的模型配置集中于此,方便站在全局的高度理解与分析模型。开发者不必关心新建的模型应该放在哪个包下面,这些在代码生成配置上都已经定义,无需时时关注。  另外,由代码生成的模型有其规范和默认已经实现的方法,方便开发者的同时,也提高模型转换、克隆复制的性能。
LeeFJ LeeFJ
1年前
Foxnic-Web 代码生成 (4) —— shadow 方法的应用
FoxnicWeb实体生成中shadow方法的应用本文探讨的是在FoxnicWeb实体模型生成时,配置枚举属性与逻辑值属性。配置枚举属性映射通过shadow方法为属性对应枚举,并同步生成get、set方法。
LeeFJ LeeFJ
1年前
Foxnic-Web 代码生成 (2) —— 代码生成的配置类
上一节,我们已经讲述了代码生成的基本步骤,但是对细节部分并未展开。利用FoxnicGenerator包进行代码生成的方式是多种多样的,我们这里提到的配置类这是其中一种,例如在FoxnicEAM项目里,有很多的代码生成非使用配置类来完成的。  我们优先选择配置类讲解,显然配置类有其优势。首先,配置类按数据表隔离,一数据表一模块一个配置类。其次,在配置类内部,按配置对象的不同,分别在不同的方法内进行配置代码的编写。例如,配置模型时在configModel方法内编写配置代码,配置字段时在configFields方法内编写配置代码。  那么,代码生成的配置为什么要用Java类,而不是用Json、XML或YML呢?首先,不管是Json、XML或YML、Java,都是在编辑器敲文本。那么哪一种方式敲文本是最方便的呢,自然是Java了,因为有开发工具强大的支持。
GoCoding GoCoding
3年前
OpenAPITools 实践
可以依据RESTAPI描述文件,自动生成服务端桩(Stub)代码、客户端SDK代码,及文档等。其是社区版的,差异可见:。本文将从零开始设计和编写API文件,并生成GoGin服务端代码,与PythonSDK代码。更多语言或框架,也是一样操作的。快速开始先熟悉下工具,直接用官方Docker镜像生成Petstore样例的G
Stella981 Stella981
3年前
JeecgBoot 2.4 微服务正式版发布,基于SpringBoot的低代码平台
项目介绍JeecgBoot是一款基于代码生成器的低代码平台!前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatisplus,Shiro,JWT支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发!JeecgBoot引领新的低代码开发模式(OnlineCoding
Stella981 Stella981
3年前
JeecgBoot 2.4.2 积木报表版本发布,基于SpringBoot的低代码平台
项目介绍JeecgBoot是一款基于代码生成器的低代码平台!前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatisplus,Shiro,JWT支持微服务。强大的代码生成器让前后端代码一键生成!JeecgBoot引领低代码开发模式(OnlineCoding代码生成手工M
LeeFJ LeeFJ
1年前
Foxnic-SQL (14) —— DAO 的 Service 扩展
FoxnicSQL中的Service有点像DDD中的Repository,但Foxnic体系里面又没有将Repository和Service区分开来,所以它更有点像两者的合体。但,他们的合与分本身是弹性的,具体还是要看业务场景的需要。在很多项目中,好多时候,Controller是Service的二传手,或许它也会成为Repository的三传手。所以,到底是单传还是二传或是三传还是要看项目、看场景。<br/FoxnicSQL中的Service就是将数据操作的目标具体化,它初始的样子就是针对单个表、单个实体的数据操作者。Service在使用时需要代码生成工具由数据表生成Po、Vo对象,Service接口以及接口实现。关于如何生成这些代码,我们不在此节展开。在此我们主要是了解如何使用Service已经为开发者提供的诸多功能