# bsin-codegen
**Repository Path**: geekjh_enterprise/bsin-codegen
## Basic Information
- **Project Name**: bsin-codegen
- **Description**: 以bsin-paas-all-in-one项目为例,演示如何使用nop-cli代码生成器来为第三方项目增加与Maven集成使用的、模型驱动的代码生成功能。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 3
- **Created**: 2023-03-23
- **Last Updated**: 2023-03-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 集成Nop平台的代码生成器
本项目以[bsin-paas-all-in-one](https://gitee.com/s11e-DAO/bsin-paas-all-in-one)项目为例,演示如何使用nop-cli代码生成器来为第三方项目增加模型驱动的代码生成功能,并于Maven打包集成在一起使用。
> Bsin-PaaS(毕昇) 是一套企业级的低代码、零代码去中心化应用搭建平台,可帮助企业快速搭建有竞争力的业务中台、流程中台、业务前台。
## 架构特点
与其他的代码生成方案相比,nop-cli具有如下特点:
1. **直接读取Excel格式的模型文件**:可以与maven打包过程集成在一起,确保模型驱动是整个DevOps流程的一个有机组成部分。而**一般的代码生成器要么使用IDE插件,要么需要单独部署应用,难以和DevOps集成**。
2. **强大的模板语言**:使用Nop平台内置的Xpl模板语言进行代码生成,可以通过nop-idea-plugin插件进行断点调试,可以通过标签库来对常用的逻辑进行抽象,并可以通过Nop平台内置的delta定制机制对已有的模板进行差量化定制。Xpl模板内嵌的脚本语言类似JavaScript,并可以和Java语言无缝集成,性能也大幅优于FreeMarker模板语言。
3. **模板数据驱动**:无需编写Java代码,只要指定一个模板文件目录,调整模板目录结构即可控制代码生成过程中的嵌套循环和判断逻辑。例如**控制某些条件满足的时候才生成对应文件**。
4. **差量定制**:无需编程,**通过增加差量模型文件即可为已有模型对象增加扩展属性**,在Excel模型文件中可以对这些扩展属性进行配置。
5. **自定义模型**:Nop平台提供了通用的Excel模型读取机制,无需编程,只需要定义一个imp.xml结构描述文件,即可读取用户自定义的Excel模型,而不用限定只能使用平台内置的模型。
## 技术原理
Nop Platform 2.0是基于可逆计算原理从零开始构建的新一代低代码平台,它致力于克服低代码平台无法摆脱穷举法的困境,从理论层面超越组件技术,有效的解决粗粒度软件复用的问题。
- gitee: [canonical-entropy/nop-entropy](https://gitee.com/canonical-entropy/nop-entropy)
- github: [entropy-cloud/nop-entropy](https://github.com/entropy-cloud/nop-entropy)
- 开发示例: [tutorial](https://gitee.com/canonical-entropy/nop-entropy/blob/master/docs/tutorial/tutorial.md)
**Nop平台的代码生成器nop-cli可以独立于Nop平台单独使用**。它的设计采用了增量式、数据驱动的代码生成方式。具体设计原理参见我的文章:
https://zhuanlan.zhihu.com/p/540022264
## 使用教程
将Nop平台的代码生成工具nop-cli集成到自己的项目中非常简单,只需要按照如下步骤执行。
### 1. 制作模板工程
首先建立一个模板工程,例如bsin-codegen-template。这个工程的作用是将一些模型文件打包成Java模块,其中并不包含任何Java代码。
> 关于代码生成语法的详细介绍,可以参见文档[codegen.md](https://gitee.com/canonical-entropy/nop-entropy/blob/master/docs/dev-guide/codegen.md)

在`resources/_vfs`目录下建立模板文件。模板文件的后缀名会被识别
* xgen后缀的文件表示将会根据此模板生成对应的文件。例如pom.xml.xgen生成pom.xml
* xrun后缀的文件表示会执行其中的Xpl模板代码,但是不会生成对应的文件
* 其他后缀表示直接原样拷贝
* @init.xrun是整个模板目录的初始化文件。当我们要渲染某个模板目录时,总会先执行目录下的@init.xpl来初始化模板执行的变量环境
#### 条件判断
在模板路径中,通过`{varA.propB}`这种形式来表示从上下文中读取变量varA的属性propB。可以通过符号!表示取反。例如`{!a}`,如果a是true或者非空的值,则!a返回false。如果返回值为false,则表示该子路径需要被忽略。例如

{!!entityModel.tagSet.@biz}表示判断数据表的标签列表中是否包含biz标签,如果不包含,则最终返回false。因此,**只有当明确标注了biz标签,才会为对应的数据实体生成Biz对象**。
### 嵌套循环
在orm模板的`@init.xrun`初始化脚本中,创建了如下变量
```java
builder.defineGlobalVar("ormModel",ormModel);
builder.defineGlobalVar("appName", appName);
builder.defineGlobalVar("moduleId",moduleId);
builder.defineGlobalVar("moduleName",moduleId.replace('/','-'));
builder.defineGlobalVar('mavenGroupId',ormModel['ext:mavenGroupId']);
builder.defineGlobalVar('mavenArtifactId', ormModel['ext:mavenArtifactId']);
builder.defineGlobalVar("moduleClassPrefix",moduleId.replace('/','_').$camelCase(true))
builder.defineGlobalVar('basePackageName', pkgName);
builder.defineGlobalVar('basePackagePath', pkgPath);
builder.defineLoopVar("entityModel","ormModel", model => model.entityModels);
builder.defineLoopVar("dialect","ormModel",model=> model["ext:dialect"].$toCsvSet());
builder.defineLoopVar("dict","ormModel", model=> model.dicts);
```
builder对应于NestedLoopBuilder类,它是用于辅助创建嵌套循环对象的帮助类。
* defineGlobalVar定义在模板路径和模板代码中可以使用的全局变量
* defineLoopVar用于定义嵌套循环变量。例如上例中定义 entityModel = ormModel.entityModels。但我们在路径中写{entityModel.shortName}时,它实际表示会从ormModel获取到entityModel的列表,然后对每个entityModel执行模板渲染。
### 2. 制作Excel模型文件
在model目录下,我们创建一个Excel数据模型文件bsin-demo.orm.xlsx。orm.xlsx这个文件后缀表明该文件是ORM数据模型定义文件,将会应用orm.imp.xml这个导入模型来解析文件中的字段数据。
在这个Excel文件中,我们可以定义数据库表、字段、表关联、唯一键等各类信息。详细配置规则参见文档[excel-model.md](https://gitee.com/canonical-entropy/nop-entropy/blob/master/docs/dev-guide/model/excel-model.md)
#### 逆向工程生成Excel模型文件
可以利用nop-cli工具从现有的数据库中导出数据模型。
* 下载 [nop-cli-2.0.0-BETA.1.jar](https://repo1.maven.org/maven2/io/github/entropy-cloud/nop-cli/2.0.0-BETA.1/nop-cli-2.0.0-BETA.1.jar)
* 在命令行中执行如下指令
```shell
java -jar nop-cli-2.0.0-BETA.1.jar reverse-db bsin -c=com.mysql.cj.jdbc.Driver --username=nop --password=nop_test --jdbcUrl="jdbc:mysql://127.0.0.1:3306/bsin?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"
```
以上指令表示连接到本地的MySQL数据库,导出bsin模式下的所有表定义。连接使用的用户名为nop,密码为nop_test。
在导出的Excel模型的基础上我们增加更多应用相关的配置,例如配置字段是否在前台显示,对应的数据域等。
### 3. 配置exec-maven-plugin插件
将模板工程成功编译打包之后,可以通过exec-maven-plugin插件来使用这个模板工程。
```xml
me.flyray.bsin
bsin-codegen-template
1.0.0-SNAPSHOT
org.codehaus.mojo
exec-maven-plugin
3.0.0
precompile
generate-sources
java
${project.basedir}
precompile
false
false
compile
true
true
true
io.nop.codegen.task.CodeGenTask
false
io.github.entropy-cloud
nop-cli
2.0.0-BETA.1
```
exec-maven-plugin是Maven自带的插件。它可以动态执行指定Java类的main方法。通过以上配置,我们实际执行的是io.github.entropy-cloud:nop-cli这个包中的CodeGenTask类的main方法,传入两个参数:当前工程所在目录,以及待执行的模板目录名,例如precompile表示执行当前工程的precompile目录下的所有模板文件。
在bsin-demo/bsin-demo-codegen模块的precompile目录下,我们可以增加编写代码生成逻辑:
```xml
```
这段代码的含义是构造一个子代码生成器,读取model目录下的bsin-demo.orm.xlsx模型,然后应用模板工程中定义的`/bsin/templates/orm`模板目录,生成代码到targetDir目标目录下。
因为plugin的配置较为复杂,所以一般我们建立一个单独的xxx-codegen子模块,在它的pom文件中增加代码生成器的调用。然后其他模块只要在scope=test模式下引用xxxx-codegen模块即可确保codegen是第一个被编译的模块,它生成的代码在后续工程的编译过程中会被使用。
#### 直接使用nop-cli工具
nop-cli命令行工具可以引入外部模板目录,例如
```shell
java -Xbootclasspath/a:bsin-codegen-template/src/main/resources/ -jar nop-cli-2.0.0-BETA.1.jar gen bsin-demo/model/bsin-demo.orm.xlsx -t=/bsin/templates/orm -o=bsin-demo
```
通过`-Xbootclasspath/a:bsin-codegen-template/src/main/resources/`引入外部的jar包或者目录到classpath中,然后通过`-t=/bsin/templates/orm`来引用classpath下的模板文件,就可以生成代码。
一般情况下,生成的文件中会增加一个codegen工程,例如bsin-demo-codegen,其中配置exec-maven-plugin插件,这样以后就可以直接通过maven install来生成了。
### 4. 扩展已有模型
基于可逆计算的基本原理,Nop平台内置了Delta差量定制机制。通过这一机制,我们可以对Nop平台内置的所有模型文件进行定制。
例如orm.xlsx文件的解析规则由orm.imp.xml文件来定义。如果我们希望在Excel模型中增加自定义的扩展属性,则可以定制orm.imp.xml文件。
例如,在bsin-codegen-template模板工程中,我们增加了定制文件`_vfs/_delta/default/nop/orm/imp/orm.imp.xml`,它继承了平台内置的文件 (raw:/nop/orm/imp/orm.imp.xml),然后在【配置】这个Sheet页中,增加了【bsin扩展】这一扩展变量,变量的标准域类型被限制为string。

在Excel模型中我们就可以增加针对此变量的配置

在模板文件中,我们可以通过动态属性表达式来访问该扩展属性(参见模板文件 README.md.xgen)。
```xml
bsin扩展信息:
${ormModel['bsin:ext']}
```
**所有具有名字空间的属性都是扩展属性**,不带名字空间的属性是模型对象本身具有的基础属性。
### 5. 增加自定义模型
除了使用内置的ORM模型之外,我们还可以根据业务需要设计新的领域模型。Nop平台内置的Excel解析器是一个非常灵活的领域模型转换器:它可以实现Tree结构的Excel数据文件和Tree结构的领域对象之间的双向转换。我们只需要定义一个imp.xml模型文件即可实现这种转换。在imp.xml文件中,我们只需要定义字段的结构,而**不需要精确指定字段在Excel模板中的位置**。也就是说,在Excel模型文件中字段的先后顺序是不重要的,我们可以随意调整字段的顺序,删除非必填的字段,增加新的自定义字段等。
在bsin-codegen-template模板工程中,提供了使用自定义模型的例子。

* 在`/_vfs/report/imp-model`目录下增加bsin-api文件,其中的内容为/bsin/imp/bsin-api.imp.xml。它表示识别bsin-api.xlsx文件后缀后使用bsin-api.imp.xml定义的模型结构来解析Excel数据。
* 在`/_vfs/bsin/imp/bsin-api.imp.xml`文件中定义解析对象结构。
* 增加`/bsin/templates/api`代码生成模板。
* 在precompile/gen-orm.xgen文件中增加调用
```javascript
codeGenerator.withTargetDir("../").renderModel('../../model/bsin-demo.bsin-api.xlsx','/bsin/templates/api', '/',$scope);
```
它表示解析model/bsin-demo.bsin-api.xlsx模型文件,然后使用/bsin/templates/api模板目录下的模板去生成代码。
#### 查看模型解析结果
通过nop-cli的extract命令,我们可以查看模型解析得到的数据
```shell
java -jar nop-cli-2.0.0-BETA.1.jar extract bsin-demo/model/bsin-demo.bsin-api.xlsx
```