低代码平台系统设计

低代码平台

低代码现状

​ 因为云计算和各种新兴开发技术的出现,2015年在SaaS市场出现了低代码工具并获得市场的认可。通过该工具可以快速的构建企业应用,因为该工具兼顾应用程序和平台服务的特点,所以也被称为aPaaS。

​ aPaaS是用于支持垂直快速的开发和交付应用程序,简化了应用程序的准备和部署,提供了一种更便捷的构建应用程序的方法。aPaaS使用方向重点在业务流程、业务规则和业务逻辑上,为了达成最终解决方案,aPaaS可以灵活地迭代应用程序。在aPaaS平台上,应用程序变更可以增量地进行,并能立即发布,所以,当在现场进行测试并获得有价值的用户反馈,便能立即更改和发布。同时,aPaaS平台能不断重复这个循环,直到达到预期的结果,这在提高用户的采纳程度和满意度的同时,减少了成本投入。aPaaS平台的特点之一是利用可视化工具,低代码开发,降低应用构建的门槛,甚至是通过零代码开发,使非IT人员也能够构建应用程序。

​ 后台管理系统成为企业不可或缺的一部分。通过对常见管理系统进行调研和分析,不难发现管理系统中页面结构相对固定,大多为两栏布局;供功能比较类似,本质上是对数据的增删改查操作,页面样式要求不高,整体需求量庞大。本文针对管理系统页面功能和样式的稳定性,以减少代码冗余、增加代码可维护性、减轻数据可视化难度为出发点,对常见管理系统和网页构建工具进行深入调研和分析,运用软件工程思想,设计并实现基于低代码的管理系统模板库。

遇到的问题:

​ 当他们要开发一个小型Web应用时,需要搭建前后端开发框架,编写通用的业务逻辑包括:登录功能、用户权限管理和菜单管理等,设计前端界面时,对于业务系统中常出现的表单、表格和数据统计界面等,需要编写界面布局和样式,编写前端业务逻辑,这种每个系统通用的界面和业务逻辑,没有进行封装,因此需要做许多重复的工作。

解决方案:

​ 在以上背景下,本文通过对国内外低代码平台进行调研分析,结合实际需求,采用前后端分离的架构设计,前端框架设计以Vue为基础,后端框架采用Springboot技术,开发设计实现了一个Web应用开发工具,提高了开发效率。本Web应用开发工具,提供了开发平台和前后端开发框架,提供了基础组件、业务逻辑封装和系统界面生成能力。对于不熟悉Web应用开发的开发者,可以通过平台进行数据库配置,生成Web应用基本的表单、表格统计和数据可视化界面,生成后台业务逻辑,对于简单的业务系统,开发人员通过YAML配置便可生成系统界面。

​ 对于缺乏前端开发经验的开发人员,开发工具提供了前端框架,提供各种前端组件库,支持直接引入,生成动态UI界面,开发者引入调用相关组件,便可实现其功能,对于基本Css样式进行了封装,减少开发人员对于界面样式和风格的设计,提高开发效率。

​ 对于有经验的开发人员,开发工具也支持二次开发,开发工具提供了可扩展的前后端接口,支持前端自定义界面的扩展,支持前端界面的集成引入,支持后端自定义业务逻辑的编写。

​ 同时开发工具对业务系统的通用模块进行了抽取和封装,例如用户权限、菜单、登录等,提供了丰富的组件和公共方法供开发者使用,开发者基于此开发工具进行二次开发,可以提高开发效率。

​ 该开发框架目前已经应用于多个Web应用的开发,包括…

国内外涌现了不少低代码平台(aPaaS 平台):

市面上目前存在三类低代码产品:

1、aPaaS+多引擎类

特点:

​ 这类低代码通常都包括了一些已经被开发出来的“引擎”,可以被直接使用或开发(调用),这些应用主要包括以下几类:BI引擎、BPM引擎、表单引擎、表格引擎、AI引擎(agent)等等,另外系统提供了用户的权限管理、用户管理、团队管理、运维管理等基础设施,可以直接在整套系统里面添加代码和拓展新功能。

优点:如果使用成熟的引擎,当然效率非常高,直接上手就能用。

比较方式:

​ 看谁的引擎最成熟,设计最好,可拓展性高。从这几个方面来说,个人觉得 织信Informat 可能是这个领域里面做的比较不错的,因为他这个产品本身的能力边界就足够强大,然后又有各大功能的加持。基本上稍微复杂的系统,他都可以满足,这是其他低/无代码产品所不具备的。

2、代码生成类

特点:可以直接生成部分前端、后台代码,有一些也能生成数据库代码;整个应用可以导出平台单独部署(这一点很关键,意思就是不用一大堆东西绑在一起,可以开发哪个导出哪个);通常都有编译器/解释器相关产品(如果有自己抽象的语言);一般都有IDE(也有一些没有,iVX、Mendix、Outsystems这些就是有IDE的)。

优点:这些产品往往开发能力比较强,有些产品甚至什么都能做,代码的压缩率很高。

缺点:虽然比学习编程语言方便很多,虽然开发也比写代码快很多,但是有一定学习成本,你可以跟其他传统的前后端代码对比,相当于要学一门新的语言,但是难度比纯代码要低很多。另外有些功能实现可能会比较绕,修修改改的过程比较耗费时间,有时还不如直接写代码实现来得快。

3、Saas无代码产品

特点:这一类,就是国内所说的“低代码/无代码”中的“无代码”,其实理解起来比较简单。目前很多无代码平台通常都是以SaaS模式来运作,平台上有构建了很多现成应用模版,用户一年只需要花几千或者几万块钱,就能直接拿这些应用模版修修改改,以保持自身的业务能顺利进行。

优点:“简单方便”是无代码这类平台的一大优势,因为它整个配置的过程中都不需要写代码,就和以前的“金数据”一样,提供丰富模版和现成组件以及接口等功能,拿过来就可以直接用,像“轻流”、“zion” 、“简搭”等都属于这一类。主要是业务人员使用的。

缺点:优势很明显,但缺点也很致命,无代码平台主要面向是业务人员,适合轻量级应用场景,如果遇到一些稍微复杂的业务流程,可能就满足不了了。

小结:

​ 无论是挑选低代码,还是无代码,都应该根据自身业务需求来选择。如果业务逻辑比较复杂,后续还要拓展更多的功能系统,那选低代码肯定稳一些。如果你内部没有IT人员,然后需求简单,想着最好是有现成功能模版直接拿来用的,那么Saas无代码产品是首选。

​ 这里,很多小白可能会进入一个误区,就是认为“低代码”,是给“业务人员”使用的或者业务人员也能用,其实“这是不可能的”。程序员和业务人员(销售、行政、运营、财务、人事等)压根儿就不是一类人,不可能一个产品适合这两类,理论上就不成立。这种讲法只是营销策略。

功能菜单

门户及导航

组织机构管理

用户管理

角色管理

数据建模

基础数据

单据

高级查询

工作流管理

消息通知

计划任务

多语言配置

  • 门户及导航
    • 门户设置
    • 导航设置
    • 导航菜单
  • 组织机构管理

  • 用户角色管理
    • 用户管理
    • 角色管理
    • 权限管理
  • 数据建模
  • 基础数据
  • 单据
    • 单据定义
    • 动作
    • 公式
    • 附件
  • 单据列表
  • 自定义查询
  • 工作流管理
    • 工作流定义
    • 工作流设计
    • 发布
    • 更新已发布
    • 导入导出
    • 业务绑定
    • 流程监控
    • 代办列表
  • 消息通知
  • 计划任务
  • 多语言配置

心得体会

​ 我们公司开发的低代码平台,主要面向开发人员,其次是运维和销售等人员。在使用公司的低代码平台过程中,总结了一些心得和体会,分为优势和劣势两方面。

优势:

  1. 低代码在某些方面确实解放了开发人员的生产力,比如数据建模、数据字典、业务流程管理、通用的查询场景等,相比起开发人员手动编写配置文件、Entity实体类、Controller接口等,节省了不少时间。
  2. 一些通用的页面组件、公式和动作逻辑,可以很方便的复用,节省开发时间。

劣势:

  1. 在稍复杂的交互场景、如稍复杂的页面表单,低代码不能很好的支持,此时可能选择自定义开发会更好。
  2. 过度使用统一页面设计器,会导致系统的整体页面样式千篇一律,像是每个页面都长一个样子。体验效果比较平淡无奇。
  3. 在后期开发过程中,如遇到低代码平台相关的报错,有时排查起来相对困难,可能需要懂低代码平台原理的开发人员才可以短时间排查出问题所在。
  4. 有一定的学习成本,系统中存在一些复杂和隐藏配置项,需要参考相关指导手册或其他人指导才能完成。

统一概念

​ 低代码平台在架构设计上分为业务构建中心运行期两个单独的服务,两个服务又是前后端分离开发和部署的。

​ 业务构建中心对应一个工作空间存储目录 workspace,构建中心的产物就是产生一系列的运行期服务所需要的元文件(主要为json 格式),此外设计期系统本身也有一个自己的元文件,也就是 product_dev_components.json 文件(产品开发组件,后面简称 PDC元文件)。构建中心系统是支持多工作空间的,每个工作空间的数据是完全隔离的。每个工作空间的根目录都会对应一个 PDC 元文件,这个元文件的数据由运行期系统维护,不同的key 对应不同的元数据。运行期系统是如何维护 PDC元文件的设计细节会在后面讲解。

​ 构建中心设计好内容,点击发布,会把元文件进行压缩打包,以 ZIP 包的方式通过网络推送给运行期,运行期接收到之后进行解压,然后经过一系列操作,将元文件转化为元数据,从而支持系统运行时热更新元数据。

# 自动初始化并更新仓库中的每一个子模块:包括 np/amino/bap/inz/buildCenter 五个子模块(初始化时间较长)
git clone --recurse-submodules https://gitee.com/Jackpotsss/bee.git
# 
git clone https://gitee.com/Jackpotsss/bee.git

# 拉取并更新某个子模块的代码
git submodule update --remote amino

# 添加子模块
git submodule add https://gitee.com/Jackpotsss/bee.git

业务构建中心

多工作空间

业务构建中心支持多工作空间,即可以实现一个业务构建中心为多个运行时服务提供元数据设计的能力。

工作空间根目录下需要一个配置文件 config.json:

{
  "workspaces" : [ 
  {
    "name" : "__default_workspace__",
    "gmsUrl" : "http://localhost:8189"
  }, {
    "name" : "wyc",
    "gmsUrl" : "http://10.2.113.162:8180"
  } ]
}

工作空间目录

每个工作空间的目录结构如下:

工作空间目录
├─__system__ //系统模块
│  ├─src
│  │  └─main
│  │     ├─metadata //元数据、命名资源存放目录
│  │     └─resources //普通资源存放目录
│  └─.cache //metadata目录下元数据、命名资源的信息缓存。如意外损坏,要通过GMS运行时服务重新同步。
└─custom.metadata //默认元数据模块
   ├─src
   │  └─main
   │     ├─metadata //元数据、命名资源存放目录
   │     └─resources //普通资源存放目录
   │        └─META-INF
   │           └─JMA.MF //元数据模块描述文件
   ├─.cache //metadata目录下元数据、命名资源的信息缓存。会自动创建并更新维护。
   └─build.gradle //构建元数据包的脚本

工作空间下包括两个模块,系统模块__system__和默认元数据模块custom.metadata

  • 系统模块用来存放从运行时服务同步过来的元数据据、资源等内容。
  • 默认元数据模块用来存放通过业务构建中心创建的元数据、资源等内容。

文件分类

工作空间下的文件分为几种类型:

  • 命名资源文件:存放在metadata目录下,根据资源的名字,对存放路径、文件名有要求。
  • 元数据文件:后缀名为.meta.json,文件内容符合json格式的元数据规范,包含nametitle(非必须)、description(非必须)、define等信息。
  • json资源文件:后缀名为.res.json,包含nametitle(非必须)、description(非必须)、define(非必须,建议)等信息。
  • 普通资源文件:存放在resources目录下,不限制存放路径、文件名字、文件类型,访问时以路径(相对于resources的路径)来访问。
  • 系统文件:如.cachebuild.gradleJMA.MF等。

元数据、命名资源存放目录metadata

  • 所有有名字的元数据文件和资源文件都保存到这个目录下。
  • 元数据、命名资源的内容要通过专门的API来访问,访问时需要指定名字。
  • 元数据、命名资源的名字是以.分隔的多段名字组成,最后一段为简单名,前面的是包名。每一段包名都对应存放那一级的目录名。
  • 元数据文件的文件名是"简单名"+".meta.json"
  • json资源的文件名是"简单名"+".res.json"

资源文件目录resources

  • 这个目录主要用来存放普通资源文件,以及各编辑器操作需要的文件。

INFO文件内容

目录描述文件 .dir.info

{
  "name": "目录名",
  "title": "目录标题",
  "description": "目录说明",
  "category": "dir",
  "properties": {
    "key": "value" //业务定义属性
  }
}

category是用来描述资源分类的属性,现在支持4个值:

  • dir:目录
  • common-resource:普通资源
  • metadata:元数据
  • json:json资源

*元数据/命名资源描述文件 .info

{
  "name": "资源名称",
  "title": "资源标题",
  "description": "资源说明",
  "category": "metadata|json",
  "define": "资源类型定义",
  "define-version": "资源类型定义版本",
  "properties": {
    "key": "value" //业务定义属性
  }
}

产品开发组件元数据

​ 每个工作空间根目录都有一个 product_dev_components.json 元文件(产品开发组件,下面简称该文件为 PDC 元文件)。PDC元文件的数据是由运行期的前端和后端分别收集的,构建中心拿到数据后持久化到工作空间进行存储。

​ 运行期系统启动后,由前端发起调用 POST /product/components 接口,并在接口中将前端扩展的组件以 Json 格式写入Body,收集系统中所有的元数据类型并缓存起来,包括但不限于单据动作、单据动作监听器、函数、查询处理器、单据主表基类数据模型(作为数据模型父类)等。

​ 构建中心刷新,会调用运行期后端接口: GET /product/components ,获取开发组件组件元数据;

具体代码查看运行期后端的 DevController

PDC 元文件内容:

{
    /** 运行期前端收集的组件*/
	"billControl" : [ {} ],
	"billAction" : [ {} ],
	"actionFunction" : [ {} ],
    "queryAction" : [ {} ],
	"queryCustomColumn" : [ {} ],
	"queryDynamicDateValue" : [ {} ],
	"queryToolBars" : [ {} ],
	/** 运行期后端收集的组件*/
	"global-functions" : [ {} ],
	"system-functions" : [ {} ],
	
	"bill-system-actions" : [ {} ],
	"bill-actions" : [ {} ],
	"bill-global-actions" : [ {} ],
	
	"bill-system-listeners" : [ {} ],
	"bill-listeners" : [ {} ],
    
    "bill-master-bases" : [ {} ],
	
	"query-enhance-processor" : [ {} ],
	"query-condition-processor" : [ {} ],
}

开发组件收集

前端组件收集

前端从emcon 配置文件中读取已注册的组件信息

后端组件收集

DevComponentsInfoProvider 接口是用来与PDC 元文件进行交互的核心接口,它负责提供全部要运行期往设计器同步的元数据信息。

目前主要实现:

BillDevMasterBaseInfoProvider 单据主表基类数据模型
BillActionsInfoProvider		单据动作
BillActionListenersInfoProvider	单据动作监听器
QueryProcessorProvider  查询增强处理器提供器
FunctionInfoProvider	公式

配置参数

运行期的配置参数 DevProperties:(前缀:com.beecode.dev)

参数 描述 默认值
alwaysCreateProductDevComponents 每次调用POST接口查询,是否总是创建 false
clearProductDevComponentsWhenUsed false
workspace 设计期的工作空间,用于隔离

查询条件处理器

QueryProcessorProvider 负责将实现 QueryConditionProcessor 接口的Spring Bean收集起来

函数收集器

FunctionInfoProvider 收集器负责将实现 IFunction 接口的Spring Bean收集起来

因为函数在系统中多处都有应用,这里也会根据Key 分类收集,

现在系统中存在四类函数:

  • 系统函数
  • 全局函数
  • 单据函数
  • 查询函数

单据函数,专门为单据编写的函数要继承BillFunction ,并注册为Spring Bean,在PDC元文件中对应的key 为 “bill-functions”。

查询函数,专门为查询编写的函数要继承QueryFunction,并注册为Spring Bean,在PDC元文件中对应的key 为 “query-functions”。

全局函数,实现 IFunction 接口,并且不是上面两种类型的函数,会被分类为全局函数。在PDC元文件中对应的key 为 “global-functions”。

系统函数是通过硬编码写死的,一般很少进行扩展。在PDC元文件中对应的key 为 “system-functions”。

FunctionInfoProvider 收集器

FunctionInfoKeyDetector   检测器
	BillFunctionInfoKeyDetector		"bill-functions"
	QueryFunctionInfoKeyDetector	"query-functions"

数据建模

仿照 BillMasterBase

第一步,编写entity类

@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Tuplizer(entityMode = EntityMode.KOBJECT)
@DevBillMasterBase(name = BillConstants.BILL_MASTER_BASE, title = "通用单据主表基类", path = "单据/通用")
public abstract class BillMasterBase extends BillEntityBase {

	/**
	 * 创建时间
	 */
	@Column(name = "create_time", nullable = false)
	@Attribute(title = "创建时间")
	private Instant createTime;
	
    //....
}

注意:只有标注 @DevBillMasterBase 注解的组件才能被收集到;

第二步,编写jmx 元文件:

<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://www.beecode.cn/schema/amino-metadata"
	xmlns:model="http://www.beecode.cn/schema/bcp-type"
		https://www.beecode.cn/schema/bcp-type/bcp-type-1.0.xsd">
	<specification>1.0</specification>
	<id>72e4f3b3-5b0b-4678-81cb-0296b04eba00</id>
	<name>com.beecode.bap.bill.entity.BillMasterBase</name>
	<title>BillMasterBase type</title>
	<define>bcp.type.Class</define>
	<define-version>1.0.0</define-version>
	<dependency>javax.persistence.Column</dependency>
	...
	<content>
		<model:class>
			<model:java>
				<model:type>com.beecode.bap.bill.entity.BillMasterBase</model:type>
				<model:fields>all</model:fields>
			</model:java>
		</model:class>
	</content>
</metadata>

第三步,编写Hibernate 映射文件:

<?xml version="1.0" encoding="UTF-8"?>  
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping
		http://www.hibernate.org/xsd/hibernate-mapping/hibernate-mapping-4.0.xsd">

	<class entity-name="com.beecode.bap.bill.entity.BillMasterBase" abstract="true" table="bill_master" optimistic-lock="version">
		<tuplizer entity-mode="dynamic-map" class="com.beecode.bcp.store.hibernate.KObjectEntityTuplizer"/>
		<id name="id" type="uuid-binary" column="id" length="16"/>
		<version name="recver" type="long"/>
		<property name="createTime" type="Instant" column="create_time" not-null="true" index="_createtime_idx" />
		<property name="billState" type="int" column="bill_state" not-null="true" index="_billstate_idx" />
	</class>
</hibernate-mapping>

注意,class 标签的 abstract 属性值为 true,表示该模版基类不生成一张单独的表;

模块缓存

.cache 是JSON格式的文本文件,位于每个元数据模块的根目录下,相当于本元数据模块的目录。位置:

workspace\__default_workspace__\__system__\.cache

同步元数据

​ 具体逻辑就是业务构建中心向运行时服务发送请求,请求体为系统模块的缓存数据,也就是 __system__ 模块下的 .cache 文件。运行时服务接到请求后,查询系统全部的元数据,然后与请求参数进行比对,记录哪些需要删除,哪些需要更新,然后返回给业务构建中心,构建中心根据返回结果进行缓存数据的更新。

发布

​ 业务构建中心将当前工作空间的元文件进行打包,传输给运行时服务。

导出元数据包

​ 业务构建中心将当前工作空间的元文件进行打包,下载到Web客户端。

元数据设计

元文件定义

​ 现系统内同时存在两种类型的元数据文件,分别是XML格式和JSON格式的元数据文件。最早存在只有XML格式的元数据文件,我们称之为1.0版本,XML格式的文件只能通过开发人员手动创建。

​ JSON格式的元数据文件是后来升级低代码平台所引入的,我们称之为2.0版本,JSON格式的元数据文件由平台自动生成。

​ 两种格式在定义元数据属性的方面,有重叠的部分,但因为2.0版本的低代码平台要承载更多的元数据信息,比如前端表单的样式、按钮摆放等,因此JSON格式的元数据包含的内容更多。

XML格式

​ 平台1.0版本通过开发人员手动创建XML格式的元数据,示例:

<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://www.beecode.cn/schema/amino-metadata"
	xmlns:m="http://www.beecode.cn/schema/bcp-type"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.beecode.cn/schema/amino-metadata
		http://www.beecode.cn/schema/amino-metadata/amino-metadata-1.0.xsd
		http://www.beecode.cn/schema/bcp-type
		http://www.beecode.cn/schema/bcp-type/bcp-type-1.0.xsd">
	<specification>JMX_1.1</specification>
	<id>ee388e5c-3ef3-469a-abd6-a3347647456b</id>
	<name>com.beecode.bcp.dict.datamodel.BasicDictRequirement</name>
	<title>BasicDictRequirement</title>
	<define>bcp.type.Class</define>
	<define-version>1.0.0</define-version>
	<dependency>javax.persistence.Column</dependency>
	<dependency>javax.persistence.Id</dependency>
	<dependency>javax.persistence.Inheritance</dependency>
	<dependency>javax.persistence.InheritanceType</dependency>
	<dependency>javax.persistence.MappedSuperclass</dependency>
	<dependency>javax.persistence.Version</dependency>
	<dependency>org.hibernate.annotations.Type</dependency>
	<dependency>com.beecode.bcp.store.annotations.Tuplizer</dependency>
	<dependency>com.beecode.bcp.store.entity.EntityMode</dependency>
	<dependency>bcp.type.constraint.StringLength</dependency>
	<content>
		<m:class>
			<m:java>
				<m:type>com.beecode.bcp.dict.datamodel.BasicDictRequirement</m:type>
				<m:fields>all</m:fields>
			</m:java>
			<m:attributes>
				<!-- 代码 -->
				<m:attribute>
					<m:annotations>
                        <m:annotation>
                            <m:type>bcp.type.constraint.StringLength</m:type>
                            <m:value>60</m:value>
                        </m:annotation>
                    </m:annotations>
					<m:name>name</m:name>
					<m:title>名称</m:title>
					<m:type>string</m:type>
				</m:attribute>
				
				<!-- 名称 -->
				<m:attribute>
					<m:annotations>
                        <m:annotation>
                            <m:type>bcp.type.constraint.StringLength</m:type>
                            <m:value>60</m:value>
                        </m:annotation>
                    </m:annotations>
					<m:name>title</m:name>
					<m:title>标题</m:title>
					<m:type>string</m:type>
				</m:attribute>
				
				<!-- 路径 -->
				<m:attribute>
					<m:annotations>
                        <m:annotation>
                            <m:type>bcp.type.constraint.StringLength</m:type>
                            <m:value>255</m:value>
                        </m:annotation>
                    </m:annotations>
					<m:name>path</m:name>
					<m:title>路径</m:title>
					<m:type>string</m:type>
				</m:attribute>
			</m:attributes>
		</m:class>
	</content>
</metadata>

JSON格式

通过低代码平台的前端操作,来自动生成对应的元数据文件。示例:

{
  "name" : "bill.testbill",
  "title" : "testbill",
  "version" : "1.0.0",
  "category" : "metadata",
  "define" : "bap.bill.Bill",
  "dependencies" : [ "bill.testbill$billcode", "datamodel.testBill" ],
  "content" : {
    "data-class" : "datamodel.testBill",
    "biz-services" : [ ],
    "actions" : [ {
      "title" : "保存",
      "isdefault" : true,
      "name" : "bill.Basic.save",
      "action" : "bill.Basic.save",
      "description" : "",
      "biz-method" : {
        "interface-name" : "bill.Basic",
        "method-name" : "save"
      },
      "before-formulas" : [ ],
      "after-formulas" : [ ],
      "listeners" : [ {
        "listener-class" : "",
        "parameters" : [ ]
      } ]
    }, {
      "title" : "创建",
      "isdefault" : true,
      "name" : "bill.Basic.create",
      "action" : "bill.Basic.create",
      "description" : "",
      "biz-method" : {
        "interface-name" : "bill.Basic",
        "method-name" : "create"
      },
      "before-formulas" : [ ],
      "after-formulas" : [ ],
      "listeners" : [ {
        "listener-class" : "",
        "parameters" : [ ]
      } ]
    } ],
    "formulas" : [ ],
    "dataType" : "datamodel.testBill",
    "bill-code" : {
      "serial" : "bill.testbill$billcode",
      "trigger" : "on_each_save"
    },
    "config" : {
      "lockCard" : ""
    },
    "forms" : [ {
      "name" : "bill.testbillui",
      "formulas" : [ ]
    } ]
  }
}

元数据存储

相关代码位置:

project: amino.core

package: com.beecode.amino.metadata.internal.repo

元文件

XML元数据存储在各个工程的classpath下,分散存储。

设计期平台将设计好的元数据打包为ZIP格式的压缩包,通过网络发送给运行期的后端服务。

运行期后端将压缩包解压到一个git库中(自动初始化git库)。

设计git库

系统会在 aminoHome目录下建一个 .repo 文件夹,该文件夹内是系统读取元数据文件的真实位置,并使用git 管理。

为什么使用 git 管理,因为这样可以很方便的查找出哪些元数据模块需要新增、修改和删除了。详细细节查看MetadataRepository

远程仓库

具体代码位置:MetadataRepositoryImpl.afterPropertiesSet()

配置项: 这个URL是git仓库的地址

amino.metadata.url

元数据

元数据按模块加载进来

在多租户环境下,要根据租户的不同设置进行个性化加载。

元数据模块

元数据解析

元数据是按照模块 MetadataModule 进行分类和加载的。在工程中,一个MetadataModule 就是一个单独的Java工程。

MetadataMech

MetadataMech 提供了创建元数据,校验元数据,创建Bean、配置Bean的接口:

该接口和 Spring 框架的 FactoryBean 非常相似。

public interface MetadataMech<T> extends Namable, Comparable<MetadataMech<?>> {

	String getName();

	Class<? extends T> getBeanClass();

	Version getVersion();

	Metadata createMetadata(Resource resource) throws MetadataBeanException;

	T createBeanInstance() throws MetadataBeanException;

	void configBean(Metadata metadata, Object bean, boolean staticBean) throws MetadataBeanException;

	void validate(Metadata metadata) throws MetadataBeanException;
}

两个核心实现:

AbstractMetadataMech -》JmxMetadataMech

JmxMetadataMech 中可以同时解析 xml 和json格式的元数据文件。

元数据类型

拓展Mech Mech Bean 名称 Bean 类型 描述 是否依赖KClass
KClassMech bcp.type.Class KClass 数据模型
BillMech bap.bill.Bill BillDefine 业务单据 依赖 KClass
DictMech bcp.dict Dict 数据字典 依赖 KClass
PrivilegeMech bcp.authz.privilege Privilege 权限项
QueryDefinitionMech inz.query.Query QueryDefinition 查询定义 依赖 KClass
FunctionTreeDefinitionMech inz-functionTree FunctionTreeDefinition 功能树
WorkflowMetaMech bcp.workflow Workflow 工作流
ParticipantStrategyDefinitionMech ParticipantStrategyDefinition 参与者策略
ImportTemplateJmxMetadataMech bcp.importTemplate ImportTemplate 导入模板 依赖 KClass
SerialMech bcp.serial.Serial Serial 序列号
KAnnotationMech bcp.type.Annotation KAnnotation
KEnumMech bcp.type.Enum DefaultKEnum

beanFactories

StaticMetadataContextImpl

​ container = DefaultMetadataDefinitionContainer

​ ctx = AnnotationConfigServletWebServerApplicationContext

DefaultListableBeanFactory

StaticMetadataBeanDefinitionRegistryProcessor

Scan static metadata files
Found 182 JMX metadata files, 2 JSON metadata files, 0 JSON resource files
Register static JMX metadata beans
Register static JSON metadata beans

BeanPostPrcessor

MetadataBeanPostProcessor

​ Bean容器初始化完成后,使用 MechRegistry 将所有的 MetadataMech 收集起来。

元数据加载

入口:

InternalAmino.start(getApplicationContext());

​ AminoApplicationRunner 是一个 ApplicationRunner类型的Bean,应用启动完成后回调该类的接口方法,从而开始Amino元数据的初始化工作。

datamodel.USER_ARCHIVE

KClass kclazz = null;
		try {
			kclazz =  Amino.getMetadataRuntime().getApplicationContext().getBean(jsonObject.getString("type"),KClass.class);
		}catch(NoSuchBeanDefinitionException e) {
			kclazz =  Amino.getApplicationMetadataContext().getExtendableBean(jsonObject.getString("type"),KClass.class);
		}

元数据定义

系统中的元数据最终要在Java后端应用中注册为Spring Bean 。

Spring Bean 定义

AbstractBeanDefinition

​ GenericBeanDefinition

​ MetadataBeanDefinition

public class MetadataBeanDefinition extends AbstractBeanDefinition {

	private String moduleName;

	private Metadata metadata;
	
	public MetadataBeanDefinition(String moduleName, Metadata metadata) {
		config(moduleName, metadata);
	}
	
	public void config(String moduleName, Metadata metadata) {
		Assert.notNull(metadata, "'metadata' must not be null");
		this.metadata = metadata;
		setFactoryBeanName(metadata.getDefine());
		setFactoryMethodName("createBeanByModuleAndName");
		ConstructorArgumentValues args = new ConstructorArgumentValues();
		args.addGenericArgumentValue(moduleName);
		args.addGenericArgumentValue(metadata.getName());
		setConstructorArgumentValues(args);
	}
}
  1. MetadataManager -》publishMetadata(File) 元数据管理器发布元数据
  2. MetadataModuleUpdate -> refreshAll -> MetadataRuntimeImpl.applyModuleChanges(List ) 元数据模块更新器按照模块刷新每个元数据模块,将变更的元数据进行配置
  3. MetadataRuntime.startModule(MetadataModule) -> MetadataModule.start() -> refreshContext() ;MetadataRuntime重启元数据模块,从而改变生命周期,最后触发刷新上下文;
  4. ModuleMetadataContext.refresh()

ModuleMetadataContext 继承自 AbstractRefreshableApplicationContext,是一个支持刷新的 ApplicationContext;

他使用 DefaultListableBeanFactory 注册 Bean,具体代码位置:AbstractModuleMetadataContext.loadBeanDefinition

org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

数据模型容器接口

DynamicTypeContainer

​ ConfigurableDynamicTypeContainer

​ DefaultDynamicTypeContainer

ApplicationContext

​ ConfigurableApplicationContext

​ AbstractApplicationContext

​ AbstractRefreshableApplicationContext

AbstractMetadataContext

​ AbstractRefreshableConfigApplicationContext

元数据缓存

元数据热更新

Metadata

元数据定义

MetadataDefinitionContainer

元数据定义容器

内置缓存,将元数据定义缓存起来

主要实现类:DefaultMetadataDefinitionContainer

MetadataModule

元数据模块,在程序中对应一个单独的工程

​ id

​ name

​ version

​ state

每个模块都有配置信息,

available-to-all-tenants 表示是否为公开模块

tenants 表示哪些租户可以使用

模块配置信息

{
	"available-to-all-tenants": false,
	"tenants":[
		{
			"id":"",
			"available": true,
			"customized": true,	//为true表示为该租户定制化的模块
		}
	]
}

ModuleContainer

用来获取所有元数据模块MetadataModule 的接口

Optional<MetadataModule> get(String name);

Optional<MetadataModule> get(String name, Version version);

Optional<MetadataModule> getMatch(String name, Boundary<Version> lowerVersion, Boundary<Version> upperVersion);

List<MetadataModule> getAll();

List<MetadataModule> getAll(int state);

default List<MetadataModule> getActives() {
	return getAll(MetadataModule.ACTIVE);
}

MetadataContext

ApplicationMetadataContext

应用服务的元数据上下文。可以用来查找一般Java类的bean、静态元数据bean、动态元数据bean。

使用者可以通过 Amino.getApplicationMetadataContext()获取当前可用的ApplicationMetadataContext。 
•在非多租户环境下,获取到的实际上是GlobalMetadataContext。 
•在多租户环境下,会获取到与当前租户相关的ApplicationMetadataContext,查找元数据bean的范围与该租户对元数据包的可见性有关。

MetadataModuleUpdater

元数据模块更新器

MetadataRuntime

public interface MetadataRuntime extends Lifecycle {

	ApplicationContext getApplicationContext() throws IllegalStateException;

	MetadataLogger getStaticMetadataLogger();

	StaticMetadataContext getStaticMetadataContext();

	ApplicationMetadataContext getApplicationMetadataContext();

	GlobalMetadataContext getGlobalMetadataContext();

	ModuleContainer getModuleContainer();

	boolean isActive();

	boolean isStarted();

}

三种 MetadataContext:

  • ApplicationMetadataContext
  • GlobalMetadataContext
  • StaticMetadataContext

MetadataRepositoryImpl.getModuleChanges(), 该方法被两个时机调用:

  1. 系统启动时,加载所有的元数据文件
  2. 系统启动中,通过RabbitMQ监听元数据消息变更消息,达到元数据动态更新的目的。

详情查看 MetadataMessageListener 监听器;监听器会根据接收到的指令,做出两种动作:

  1. 刷新全部元数据
  2. 只更新需要新增、修改和删除的元数据模块;

监听器使用 MetadataModuleUpdater 元数据模块更新器来选择全量刷新还是部分更新元数据,详情查看 MetadataModuleUpdaterImpl.refreshAll()updateToHead 方法,

接着调用 InternalMetadataRuntime.applyModuleChanges 方法将元数据更新,ModuleContainer 是实际存储元数据的地方,

元数据容器内置了多个缓存,用于缓存元数据信息

可以获取 MetadataContext

元数据状态变化:

检查元数据–》STARTING

启动–》 ACTIVE

如果启动过程中发生异常-》RESOLVED

启动日志:

com.beecode.amino.core.AminoHome - Using Amino home directory: E:\code\gov\amino_home
com.beecode.amino.metadata.repo.MetadataRepository - Open metadata repository E:\code\gov\amino_home\.repo

数据表热更新

系统使用Hibernate 作为ORM框架,利用Hibernate 重新加载SessionFactory 来实现数据表热更新。

元数据生命周期

元数据Bean在生命周期中会经历创建、初始化、完成、使用、销毁等5个阶段。

创建 MetadataBean

InitializingMetadataBean

CompletingMetadataBean

DisposableMetadataBean

public abstract class MetadataBeanSupport implements 
	MetadataBean, InitializingMetadataBean, CompletingMetadataBean, DisposableMetadataBean {
		
}

创建Bean、初始化Bean 的逻辑在 AbstractMetadataMech.createBeanByModuleAndName 方法中;

初始化

InitializingMetadataBean 可以初始化的元数据Bean

​ 实现了这个接口的元数据Bean会在初始化元数据Bean的时候,在init(InitContext, Metadata)方法中解析Metadata,根据具体元数据定义来配置元数据Bean。

public interface InitializingMetadataBean {
	void init(InitContext context, Metadata metadata) throws MetadataBeanException;
}

​ 初始化元数据Bean。 在初始化的时候,只能从Metadata中获取元数据定义信息,通过InitContext获取依赖的资源文件。 在初始化阶段是不能访问依赖的元数据Bean的,需要在complete阶段才能访问

元数据Bean定义

public class MetadataBeanDefinition extends AbstractBeanDefinition {
	private String moduleName;
	private Metadata metadata;
}
AbstractRefreshableApplicationContext
	AbstractMetadataContext
		AbstractModuleMetadataContext	
			ModuleMetadataContext

注册BeanFactory

添加Bean

public abstract class AbstractModuleMetadataContext extends AbstractMetadataContext {

	@Override
	protected DefaultListableBeanFactory createBeanFactory() {
		List<BeanFactory> beanFactories = new ArrayList<>();
		beanFactories.add(getInternalParentBeanFactory());
		getModule().getDependencies().forEach(e -> addDependencyBeanFactories(beanFactories, e));
		CompositeBeanFactory parent = new ConfigurableCompositeBeanFactory(beanFactories);
		DefaultListableBeanFactory factory = new DefaultListableBeanFactory(parent);
		//...
		return factory;
	}

	protected void loadBeanDefinition(DefaultListableBeanFactory beanFactory, Metadata metadata, 
				boolean lazy) {
		String beanName = metadata.getName();
		try {
			MetadataBeanDefinition beanDefinition = new MetadataBeanDefinition(getModuleName(), metadata);
			beanDefinition.setLazyInit(lazy);
			beanFactory.registerBeanDefinition(beanName, beanDefinition);
		} catch (Exception e) {
			logger.error("Failed to load metadata bean definition: " + beanName, e);
		}
	}
}

时机:

​ Spring 框架 refreshBeanFactory() 刷新BeanFactory,从而触发 使用BeanFactory 加载 BeanDefinitions

完成

CompletingMetadataBean 可以完成(complete)的元数据Bean

元数据Bean在经过了初始化(包括解析Metadata元数据定义、Spring框架注入值)后,就进入complete阶段。
在complete阶段,元数据可以获取依赖的其它元数据,最终完成元数据Bean的创建工作。

使用

销毁

​ 可以被处置、废弃的元数据

​ 如果在更新元数据操作中删除了元数据,元数据就进入销毁阶段,元数据Bean实现者可以在destroy(DestroyContext, Metadata)方法中做一些销毁元数据的操作。

​ 与DisposableBean不同的是,DisposableBean是一个Bean销毁时触发调用的,而DisposableMetadataBean是元数据销毁时触发的。程序运行时,元数据Bean有可能被临时的销毁然后又重新创建。在这个过程中,只会触发DisposableBean。只有当做了元数据更新操作,销毁了部分元数据后,才会触发DisposableMetadataBean。

元数据拓展

MetadataMech

MetadataMechRegistry接口,它有一个实现类

public class MetadataMechRegistryImpl extends ApplicationObjectSupport implements MetadataMechRegistry {

	private Map<String, MetadataMech<?>> registry = new ConcurrentHashMap<>();
}

项目启动,所有Bean初始完成后, MetadataBeanPostProcessor 会自动将系统中所有的元数据装配器进行注册。

public class MetadataBeanPostProcessor extends ApplicationObjectSupport 
		implements BeanPostProcessor, Ordered {

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof MetadataMech<?>) {
			getMechRegistry().addMetadataMech((MetadataMech<?>) bean);
		}
		return bean;
	}
}

元数据服务

MetadataService metadataService = ApplicationContextProvider.getContext().getBean(MetadataService.class);
			Metadata metadata = metadataService.getMetadata(name);

数据模型

​ 我们知道,单纯的Hibernate框架,支持两种实体映射类型:POJO和 动态Map,当实体类型是POJO类型时,只需要编写POJO类即可,不需要额外编写hbm映射文件;当实体类型是动态Map或者拓展的自定义类型时,只需要编写hbm映射文件,而不需要编写POJO类;

​ 在最早的第一版元数据定义中,jmx中 使用 <m:parents><m:attributes> 标签来定义元数据内容,使用xml格式编写实体映射关系,这种写法确实效率低下。

数据模型定义

实体类:

@Entity
@Table(name = "asset_dispose_bill")
@Tuplizer(entityMode = EntityMode.KOBJECT)
public class AssetDisposeBill extends WorkflowBillMasterBase {

    @Column(name = "code", length=255)
	private String code;
    
	@ManyToOne
	@JoinColumn(name = "proposer")
	@AttributeType(AssetConstants.STAFF_ENTITY_NAME)
	private KObject proposer;
    
    /** 子表 */
	@SubTable
	@OneToMany(mappedBy="master", cascade = CascadeType.REMOVE)
	@AttributeType(type = AssetDisposeBillItem[].class)
	private KObject detailItems;
    
    //getter setter....
}

数据模型元数据 AssetDisposeBill.jmx

<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://www.beecode.cn/schema/amino-metadata"
	xmlns:m="http://www.beecode.cn/schema/bcp-type"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.beecode.cn/schema/amino-metadata
		https://www.beecode.cn/schema/amino-metadata/amino-metadata-1.0.xsd
		http://www.beecode.cn/schema/bcp-type
		https://www.beecode.cn/schema/bcp-type/bcp-type-1.0.xsd">
	<specification>1.0</specification>
	<id>35b1a6c4-431a-4e2d-bbca-294217850dca</id>
	<name>com.beecode.inz.card.entity.AssetDisposeBill</name>
	<title>资产处置申报单实体</title>
	<define>bcp.type.Class</define>
	<define-version>1.0.0</define-version>
	<dependency>javax.persistence.CascadeType</dependency>
	<dependency>javax.persistence.Column</dependency>
	<dependency>javax.persistence.JoinColumn</dependency>
	<dependency>javax.persistence.Entity</dependency>
	<dependency>javax.persistence.OneToMany</dependency>
	<dependency>javax.persistence.ManyToOne</dependency>
	<dependency>javax.persistence.Table</dependency>
	<dependency>org.hibernate.annotations.Type</dependency>
	<dependency>com.beecode.bcp.store.annotations.Tuplizer</dependency>
	<dependency>com.beecode.bcp.store.entity.EntityMode</dependency>
	<dependency>com.beecode.bap.bill.annotations.SubTable</dependency>
	<dependency>com.beecode.bap.bill.entity.WorkflowBillMasterBase</dependency>
	<dependency>com.beecode.inz.card.entity.AssetDisposeBillItem</dependency>
	<content>
		<m:class>
			<m:java>
				<m:type>com.beecode.inz.card.entity.AssetDisposeBill</m:type>
				<m:fields>all</m:fields>
			</m:java>
		</m:class>
	</content>
</metadata>

hibernate 映射文件:AssetDisposeBill.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>  
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping
		http://www.hibernate.org/xsd/hibernate-mapping/hibernate-mapping-4.0.xsd">

	<class entity-name="com.beecode.inz.card.entity.AssetDisposeBill" table="asset_dispose_bill" optimistic-lock="version">
		<tuplizer entity-mode="dynamic-map" class="com.beecode.bcp.store.hibernate.KObjectEntityTuplizer"/>
		<id name="id" type="uuid-binary" column="id" length="16"/>
		<version name="recver" type="long"/>
		<property name="memo" type="nstring" column="memo" length="255"/>
		<many-to-one name="proposer" entity-name="com.beecode.bap.staff.datamodel.Staff" column="proposer" fetch="select" />
		<many-to-one name="applyOrg" column="apply_org" entity-name="com.beecode.bap.department.datamodel.Department" fetch="select" />
		<property name="enclosure" type="uuid-binary" column="enclosure" length="16" not-null="false" />
		<!-- <property name="belongId" type="uuid-binary" column="belong_id" length="16"/> -->
		<bag name="detailItems" inverse="true" fetch="select" lazy="true" order-by="ordinal">
			<key column="master_id" not-null="true"/>
			<one-to-many entity-name="com.beecode.inz.card.entity.AssetDisposeBillItem"/>
		</bag>
	</class>

KClass

获取KClass的两种方式:

//Types 类中缓存了一份所有KClass,可直接获取
KClass kClass = Types.loadClass(className);
//在Spring Bean容器中获取
KClass kClass = context.getBean(className,KClass.class);

的所有类型都在 Types 类中定义。

bap实现Hibernate接口

KObjectEntityTuplizer extends AbstractEntityTuplizer

KObjectEntityNameResolver implements EntityNameResolver
KObjectAttributeGetter implements Getter
KObjectAttributeSetter implements Setter
KObjectInstantiator implements Instantiator
KObjectProxyFactory implements ProxyFactory
KObjectProxy implements HibernateProxy
KObjectLazyInitializer extends AbstractLazyInitializer

注解类型(bcp.type.Annotation)的元数据 com.beecode.bcp.store.annotations.Tuplizer

KClass构建器,通过构建器构建的KClass不会缓存,一般供外部用来构建临时KClass。 构建器只能构建普通类型,不能构建数组类型。
TypeBuilder

TypeBuilder

​ KClass构建器,通过构建器构建的KClass不会缓存,一般供外部用来构建临时KClass。构建器只能构建普通类型,不能构建数组类型。

​ 如通过 KObjects.of 方法创建的KObject 就属于 动态KClass;

动态生成

使用 Javassist 字节码类库动态生成一个KClass 类,

位置

DefaultKClass   initStatelessDelegator();   生成一个无状态代理类

无状态代理类的内容如下:

@javax.persistence.Entity
@javax.persistence.Table(name="USER_ACCOUNT")
@com.beecode.bcp.store.annotations.Tuplizer()
class datamodel.user_account {
	@javax.persistence.Column(name="ID")
	@javax.persistence.Id
	bcp.type.uuid id;
	@javax.persistence.Column(name="RECVER")
	bcp.type.int recver;
	@javax.persistence.Column(name="NAME")
	@org.hibernate.annotations.Type(type="nstring")
	bcp.type.string name;
	@javax.persistence.Column(name="USER_CODE", length=100)
	@org.hibernate.annotations.Type(type="nstring")
	bcp.type.string userCode;
	@javax.persistence.Column(name="CREATE_TIME")
	bcp.type.datetime createTime;
	@javax.persistence.Column(name="VALIDITY_TIME")
	bcp.type.datetime validityTime;
	@javax.persistence.Column(name="UPDATE_TIME")
	bcp.type.datetime updateTime;
	@javax.persistence.Column(name="AMOUNT", scale=2, precision=12)
	bcp.type.fixnum amount;
	@javax.persistence.Column(name="STATE", length=33)
	@org.hibernate.annotations.Type(type="nstring")
	bcp.type.string state;
	@javax.persistence.Column(name="USER_ID")
	bcp.type.uuid userId;
}

KObject

//string 转KObject
KObject kObject = JSONObjectUtils.toObject(body, kClass);
//KObject 转Entity 
AssetCard card = kObject.toObject(AssetCard.class);

自定义类型转换

Tuplizer

AbstractEntityTuplizer

​ DynamicMapEntityTuplizer

​ PojoEntityTuplizer

​ KObjectEntityTuplizer

使用Entity 方式

@ManyToOne
@JoinColumn(name = "subsidy_grant_bill")
private SubsidyGrantBill subsidyGrantBill;

使用 Tuplizer 改为 KObject 之后

@ManyToOne
@JoinColumn(name = "subsidy_grant_bill")
@AttributeType(SubsidyConst.SUBSIDY_GRANT_BILL_ENTITY)
private KObject subsidyGrantBill;

KEnum

KAnno

基类扩展

JPA 定义了三种实体继承策略

  • SINGLE_TABLE 单表继承
  • JOINED 联合继承

父类和子类实体分别对应数据库中不同的表,子类实体的表中只存在其扩展的特殊属性,父类的公共属性保存在父类实体映射表中。

  • Table_PER_Class 表分离策略

​ 父类和子类实体分别对应数据库中不同的表,子类表中保存所有属性,包括从父类实体中继承的属性。

只需在父类实体的@Entity注解下添加如下注解:

@Inheritance(Strategy=InheritanceType.TABLE_PER_CLASS)

单据

系统单据服务

基础单据服务

BillBasicBizService 接口和 实现类 DefaultBillBasicBizService

系统提供的通用增删改操作就是该接口实现的。

单据元数据

AssetDisposeBillDefine.jmx 定义单据元数据:

<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://www.beecode.cn/schema/amino-metadata"
	xmlns:model="http://www.beecode.cn/schema/bcp-bill"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dev="http://www.beecode.cn/schema/develop"
	xsi:schemaLocation="http://www.beecode.cn/schema/amino-metadata
		https://www.beecode.cn/schema/amino-metadata/amino-metadata-1.0.xsd
		http://www.beecode.cn/schema/bap-bill
		https://www.beecode.cn/schema/bap-bill/bap-bill-1.0.xsd">
	<specification>1.0</specification>
	<id>1a164b39-d573-4c9c-a70a-20ff3fcf3626</id>
	<name>com.beecode.inz.card.bill.AssetDisposeBillDefine</name>
	<title>资产处置单</title>
	<define>bap.bill.Bill</define>
	<define-version>1.0.0</define-version>
	<dependency>com.beecode.inz.card.entity.AssetDisposeBill</dependency>
	<dependency>com.beecode.inz.card.bill.AssetDisposeBillSerial</dependency>
	<content>
		<model:bill>
			<model:data-class>com.beecode.inz.card.entity.AssetDisposeBill</model:data-class>
			<model:biz-services>
				<model:service-class>com.beecode.inz.card.internal.service.AssetDisposeBillServiceImpl</model:service-class>
			</model:biz-services>
			<model:bill-code>
				<model:serial>com.beecode.inz.card.bill.AssetDisposeBillSerial</model:serial>
				<model:trigger>on_first_save</model:trigger>
			</model:bill-code>
			
			<model:actions>
				<model:action>
					<model:biz-method>
						<model:interface-name>bill.Basic</model:interface-name>
						<model:method-name>create</model:method-name>
					</model:biz-method>
				</model:action>
				<model:action>
					<model:biz-method>
						<model:interface-name>bill.Basic</model:interface-name>
						<model:method-name>save</model:method-name>
					</model:biz-method>
				</model:action>
				<model:action>
					<model:biz-method>
						<model:interface-name>bill.Basic</model:interface-name>
						<model:method-name>delete</model:method-name>
					</model:biz-method>
					<model:listeners></model:listeners>
				</model:action>
				<model:action>
					<model:biz-method>
						<model:interface-name>asset.dispose</model:interface-name>
						<model:method-name>refreshItems</model:method-name>
					</model:biz-method>
					<model:listeners></model:listeners>
				</model:action>
			</model:actions>
		</model:bill>
	</content>
</metadata>

AssetDisposeBillSerial.jmx 定义单据编号元数据:

<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://www.beecode.cn/schema/amino-metadata"
	xmlns:model="http://www.beecode.cn/schema/bcp-bill"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:dev="http://www.beecode.cn/schema/develop"
	xsi:schemaLocation="http://www.beecode.cn/schema/amino-metadata
		https://www.beecode.cn/schema/amino-metadata/amino-metadata-1.0.xsd
		http://www.beecode.cn/schema/bcp-serial
		https://www.beecode.cn/schema/bcp-serial/bcp-serial-2.0.xsd">
	<specification>1.0</specification>
	<id>cbe0533a-50b6-4ee4-81d2-3a45beed8b44</id>
	<name>com.beecode.inz.card.bill.AssetDisposeBillSerial</name>
	<title>AssetChangeBillSerial</title>
	<define>bcp.serial.Serial</define>
	<define-version>2.0.0</define-version>
	<dependency>com.beecode.inz.card.entity.AssetDisposeBill</dependency>
	<content>
		<model:serial>
			<model:segments>
				<model:literal>CZSQ</model:literal>
				<model:expression>
					<model:content>int(year(today()))</model:content>
					<model:padding>
						<model:length>4</model:length>
						<model:pad-direction>left</model:pad-direction>
						<model:pad-character>0</model:pad-character>
					</model:padding>
				</model:expression>
				<model:expression>
					<model:content>int(month(today()))</model:content>
					<model:padding>
						<model:length>2</model:length>
						<model:pad-direction>left</model:pad-direction>
						<model:pad-character>0</model:pad-character>
					</model:padding>
				</model:expression>
				<model:sequence>
					<model:name>com.beecode.inz.card.bill.AssetDisposeBillSerial.seq</model:name>
					<model:min-value>1</model:min-value>
					<model:init-value>1</model:init-value>
					<model:period>MONTH</model:period>
					<model:padding>
						<model:length>6</model:length>
						<model:pad-direction>left</model:pad-direction>
						<model:pad-character>0</model:pad-character>
					</model:padding>
				</model:sequence>
			</model:segments>
			<model:expression-type>formula</model:expression-type>
			<model:expression-env>
				<model:data-type>com.beecode.inz.card.entity.AssetDisposeBill</model:data-type>
			</model:expression-env>
		</model:serial>
	</content>
</metadata>

单据定义

第一版

Biztype 接口,现已废弃

第二版

BillDefine接口

BillDefine billDefine = Amino.getApplicationContext().getBean("com.beecode.inz.card.bill.AssetEntryNewBill",BillDefine.class);
		Bill bill = billService.createBill(billDefine);
		BillData billData = bill.getData();
		billData.set("code", UUID.randomUUID().toString());
		billService.saveBill(bill);

单据定义:BillDefine

单据实例:Bill

单据动作

扩展自定义动作

在单据元数据中定义动作:

<model:bill>
 		<model:actions>
			<model:action>
				<model:biz-method>
					<model:interface-name>bill.Basic</model:interface-name>
					<model:method-name>create</model:method-name>
				</model:biz-method>
			</model:action>
           	<model:action>
				<model:biz-method>
					<model:interface-name>asset.dispose</model:interface-name>
					<model:method-name>refreshItems</model:method-name>
				</model:biz-method>
				<model:listeners></model:listeners>
			</model:action>
	</model:actions>
</model:bill>

在对应的Service 接口和实现类中:

@BizTypeInterface("asset.dispose")
public interface AssetDisposeBillService {
    //第二个参数是可变参数,如果传参类型不确定用Object接收,如果确定,可以直接使用String、Map等接收参数值
	void refreshItems(@Autoparam Bill bill ,Object... parameters);
}

@Service
@BizTypeImplement("com.beecode.inz.card.entity.AssetDisposeBill")
public class AssetDisposeBillServiceImpl implements AssetDisposeBillService{
    
    @Override
	public void refreshItems(Bill bill ,Object... parameters) {
		// 后端自动根据billId再次查询一遍完整的单据数据并传递过来,自定义的parameters参数也会传过来,方便后端处理
	}
}

调用单据通用的web接口:

POST /bill/executed/cached/{define}/{id}/{action}
示例:
/bill/executed/com.beecode.inz.card.bill.AssetDisposeBillDefine/asset.dispose.refreshItems

{
    "parameters":[
        {
            "key1":"value1",
            "key2":"value2"
        }
    ],
    //"parameters":[
    //    "value1","value2"
    //],
    "data":{
        "id":"35d6d46a-d742-45eb-b07f-0331cb3587e6"
    }
}

注:

​ parameters 参数的值可以是任意类型,支持字符串、对象、基本类型等。

​ 这样在后端的Service 的动作方法中,bill 数据也会直接传过来(后端根据billId再查询一遍完整的数据),直接操作数据。省去了写Controller接口、Service 查询数据的逻辑。

动作监听器

单据业务执行器创建

  1. 在后端创建一个实现com.beecode.bap.bill.define.BillActionListener的bean
  2. 在单据元数据中添加对应的元素

另一种实现方式:

@ActionDefinition(name="MyAction",title="我的动作",description = "我的描述")
public class MyAction implements ActionSPI{

	@Override
	public Object execute(BillContext context, Object... parameters) {
		System.out.println("MyAction.....");
		return null;
	}
}

单据公式

注册步骤:

  1. 编写前端公式
  2. mcon 中引入
  3. emcon 中注册
  4. 编写后端公式并注册为Spring Bean(继承BillFunction)

注意:前端公式和后端公式Bean名称一定要一致,注意大小写!

前端公式

//换表工单:选择用户档案后,自动给其他字段赋值
export default {
  test(context){
    //设置、获取某个全局参数
    context.setGlobalParam('applyTemplateStringData', {})
    let v  = context.getGlobalParam(GLOBAL_PARAM_KEY);
    //获取子表数据,并设置字段值
    const subTableName = arguments[0][5].FieldTableCode
    const tableObj = context.getTableType(subTableName)
    let subData = context.getSubData(tableObj.title)
    if(subData instanceof Array){
      for (const detail of subData) {
        let fylbValue =detail.getValue("fylb")
        detail.setValue(bzjehj,'')
      }
      context.refreshSubDataRow(tableObj.title)
    }
  },
  execute:function(){
    debugger;
    let selectFileObject = arguments[0][0]//选择的字段
    let _key = selectFileObject.FieldCode //字段标识
    let table = selectFileObject.FieldTableCode//字段所在表
    let context = arguments[1];
    let applyUser = selectFileObject.getResult().Value; //获取字段对应的字段值

    //获取字段对应值
    let user =context.getMasterData().getValue(_key); //获取主表某字段的值;如果字段关联的实体,则获取的是实体对象
    let userId = user.id;

    INZ.Util.invokeServer({
      path: "/user/archive/queryUserAndEqptInfo/"+userId,
      contentType: "application/json",
      type: "GET",
    }).then((response) => {
          let res = JSON.parse(response);
          let data = res.data;
          debugger
    }).catch((err) => {
        console.error(err);
        return;
    });
    return result!==null&&''!==result&&result!==undefined;
},
  getResultType:function(){
    return FMR.ConstDataTypes.General;
  }
}

公式类型

公式类型有四种:

  • 运算
  • 运算不赋值
  • 校验
  • 赋值

如果选择运算类型,公示内容的返回值会自动赋值给关联字段;

在动作定义中,设置执行前公式,会触发后端公式;

前端公式中,设置运行时执行,指的是公式中入参的字段,发生变化时,将触发前端公式,与关联字段无关。如果公式中入参3个字段,则这3个字段中在表单界面上任意一个发生值变化,就会触发前端公式;

单据编号

单据缓存

客户端如何使用

public interface BillCache {

	boolean contains(BillDefine define, UUID id);

	Bill get(BillDefine define, UUID id);

	Bill put(Bill bill);

	void release(BillDefine define, UUID id);

	void evict(BillDefine define, UUID id);
}
BillCache
SimpleBillCache
NedisBillCache

SimpleBillCache

使用 CaffeineCacheManager 作为其缓存管理器。

NedisBillCache

操作API

单元测试示例:Test_BillService

/**
 	  新增一张单据,并添加一张子表的多条明细数据
*/ 
public void insertBill() {
       BillDefine billDefine = getContext().getBean(AssetConstants.ASSET_ALLOCA_BILL, BillDefine.class);
       Bill acceptBill = billService.createBill(billDefine);
       BillData billData = acceptBill.getData();
       billData.set(F_IN_ORG,bizData.get(F_IN_ORG));
       billData.set(F_OUT_ORG,bizData.get(F_OUT_ORG));
       List<? extends BillSubData> subs = applyBillData.getSubs(ASSET_ALLOCATE_RECEIVE_DETAIL);
       for (BillSubData sub : subs) {
           BillSubData billSubData = billData.createSub(ASSET_ALLOCATE_RECEIVE_DETAIL);//新增子表明细
           billSubData.set(F_TITLE, sub.getString(F_TITLE));
           billSubData.set(F_ASSET_CAT_ID, sub.getObject(F_ASSET_CAT_ID));
           billSubData.set(F_VALUE, sub.getDouble(F_VALUE));
           billSubData.set(F_QUANTITY, sub.getInt(F_QUANTITY));
       }
       billService.saveBill(acceptBill);  //保存单据
   }
/**
 	  保持主表数据,不使用单据API,直接保持主表实体数据。
*/ 
public void insertBill(Bill bill) {
       BillData billData = bill.getData();
       billData.set(F_IN_ORG,bizData.get(F_IN_ORG));//赋值操作
       KObject bizData = ((BillDataExt) billData).toKObject();
	kObjectDao.update(bizData);
   }

基础数据

数据模型

@javax.persistence.Entity
@javax.persistence.Table(name="MD_ILLEGALTYPE")
@com.beecode.bcp.store.annotations.Tuplizer()
@javax.persistence.Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@javax.persistence.MappedSuperclass
@com.beecode.bcp.store.annotations.Tuplizer()
class datamodel.dict.illegalType extends com.beecode.bcp.dict.datamodel.BasicDictRequirement {
}

高级查询

QueryViewContorller

高级查询的npsql 所支持的函数和原生SQL 有点区别,com.jiuqi.np.sql.da.SQLFuncSpec 和 ScalarFunction 记录了所有的可以用的函数,可以参考

SELECT
    u.id,
    u.userCode,
    u.userName,
    u.location,
    u.phoneNumber,
    u.idNumber,
    (
        case u.status
        when 'NORMAL' then '正常'
        when 'DISABLE' then '停用'
        when 'CLOSED' then '销户'
        end
    )as status,
    u.createTime,
    u.creatorName,
    a.amount
from
    USER_ARCHIVE as u
    left join user_account as a on a.userId=u.id
    left join businessHall as bh on bh.id = u.businessHall
WHERE
    (u.userName LIKE :?userName or u.phoneNumber like :?userName)
    AND u.userCode LIKE :?userCode
    AND u.phoneNumber LIKE :?phoneNumber
    AND u.payType = :?conditionPayType
    AND u.location LIKE :?conditionLocation
    AND u.status =:?status
    AND bh.id = :?conditionBusinessHall
ORDER BY u.createTime desc

数据引擎

NPSQL

逻辑表

查询元数据

依赖的数据模型 KClass

数据处理器:对查询出来的数据做二次处理

场景

权限

查询列定义

查询场景

@Component
public class SubsidyNotDiscardScene implements SceneExecutor {

	@Override
	public void execute(SceneExecuteContext context) throws SceneExecuteException {
		Predicate p1 = context.getCriteriaBuilder().notEqual(context.getRoot().get(SubsidyConst.F_BIZ_STATUS), SubsidyStatus.DISCARD.name());
		Predicate p2 = context.getRoot().get("discard").isNull();
		context.getCriteriaQuery().and(context.getCriteriaBuilder().and(p1));
	}

}

查询处理器

典型场景:将枚举的英文Name 转为中文Title:

@Component
public class SubsidyDetailQueryProcessor implements DataProcessor {

	@Override
	public void process(DataProcessorContext context) {
		List<RowData> rowDatas = context.getRowDatas();
		if (null == rowDatas || rowDatas.size() == 0) {
			return;
		}
		for (RowData row : rowDatas) {
			if (row.get(SubsidyConst.F_CUSTOMER_TYPE) != null) {
				String customerType = (String)row.get(SubsidyConst.F_CUSTOMER_TYPE);
				row.put("customerTypeTitle", CustomerTypeEnum.valueOf(customerType).getTitle());
			}
			if (row.get(SubsidyConst.F_BIZ_STATUS) != null) {
				String bizStatus = (String)row.get(SubsidyConst.F_BIZ_STATUS);
				row.put("bizStatusTitle", CustomerTypeEnum.valueOf(bizStatus).getTitle());
			}
		}
	}
}

https://jsqlparser.github.io/JSqlParser/migration50.html

功能树

功能树节点支持配置权限项

主要接口:

public interface FunctionTreeDefinition {
	/**  获取父功能树定义 */
	FunctionTreeDefinition getParent();
	/** 获取功能树节点  */
	FunctionNodes getFunctionNodes();
	
	FunctionTreeDefinition clone();
	
	FunctionTreeDefinition mergeParent();
}
/** 功能树节点*/
public interface FunctionNodes {
	List<FunctionNode> getAllFunctionNode();
	Set<Privilege> getPrivilegeSet();
}

权限

权限元数据:Privilege

元数据装配器:PrivilegeMech

权限项类型:

  • 标识权限项 Token
  • 对象权限项 Object
  • 规则权限项 Rule

接口:Privilege

权限服务:PrivilegeService

三种权限项的用例在测试类中:

AuthzTest

工作流

需要配置工作流的业务单据需要继承 AbstractWorkflowBillService 抽象类,支持提交、同意、驳回三个动作。

序列号 Serial

SerialConfiguration

bean type: SerialMech
bean name: bcp.serial.Serial
metadata define:  SerialDefine
Serial   

表达式

底层使用 antlr

https://www.antlr.org

多租户

​ 所谓多租户,就是在一个应用程序中,不同的租户(Tenant)可以共享同一套应用程序的代码和基础设施,但是每个租户都可以独立地管理和控制它们自己的数据。因此,多租户应用程序需要支持在相同的数据库中存储和访问多个租户的数据,同时保证数据隔离和安全性。

实现方案

​ Hibernate 是一个广泛使用的 ORM(对象关系映射)框架,支持多租户(Multi-Tenancy)应用程序的开发。

​ Hibernate 提供了三种多租户实现策略:

  1. 独立数据库 Database
  2. 共享数据库实例,独立Schema
  3. 共享数据库Schema,共享数据表,Discriminator 通过使用标识符列实现多租户。
  1. 租户标识符(Tenant Identifier)方式:在这种方式中,每个租户都有一个唯一的标识符,该标识符用于区分不同的租户。在 Hibernate 中,可以通过实现 MultiTenantConnectionProvider 接口来实现租户标识符方式的多租户。在 MultiTenantConnectionProvider 中,可以通过从数据库连接请求中提取租户标识符,为每个租户提供独立的数据库连接。
  2. 租户架构(Tenant Schema)方式:在这种方式中,为每个租户创建一个独立的数据库架构(Schema),在这个架构中存储该租户的数据。在 Hibernate 中,可以通过实现 MultiTenantConnectionProviderCurrentTenantIdentifierResolver 接口来实现租户架构方式的多租户。在 MultiTenantConnectionProvider 中,可以通过为每个租户创建一个独立的数据库连接来实现租户架构的切换,而在 CurrentTenantIdentifierResolver 中,可以根据当前租户的标识符来选择正确的数据库连接。

需要注意的是,多租户实现方式的选择取决于应用程序的需求和实际情况,不同的实现方式有其各自的优缺点。同时,多租户应用程序的设计和实现也需要考虑到数据隔离、安全性、可扩展性等方面的问题。

业务数据隔离

对每个租户的定制化需求

AminoTenant 提供租户对元数据包的可见性的能力

Hibernate 的多租户能力

String MULTI_TENANT = "hibernate.multiTenancy";
String MULTI_TENANT_CONNECTION_PROVIDER = "hibernate.multi_tenant_connection_provider";
String MULTI_TENANT_IDENTIFIER_RESOLVER = "hibernate.tenant_identifier_resolver";

多租户环境下肯定有多个SessionFactory

public interface MultiTenancySessionFactoryProvider {

	/**
	 * 获取默认租户的SessionFactory
	 */
	SessionFactory getDefaultSessionFactory();

	/**
	 * 获取指定租户的SessionFactory
	 */
	SessionFactory getSessionFactory(String tenantId);
}

继承关系

MultiTenancySessionFactoryProvider
	AbstractMultiTenancySessionFactoryProvider
		CommonMultiTenancySessionFactoryProvider
			NpSqlMultiTenancySessionFactoryProvider

元数据隔离

配置参数:

amino.multitenancy
/**
 * 租户的个性化设置接口,可以获取租户上所有的定制信息
 */
public interface Customization {

	/**
	 * 获取扩展的元数据名字,返回一个map,key是原来的元数据名,value是扩展的元数据名
	 */
	Map<String, String> getExtendedMetadataNames();

}

其他

租户服务

TenantService 租户服务

租户服务会缓存所有活动的租户,如果不特别声明,提供的方法都是针对缓存去做操作

TenantRuntime

租户缓存

TenantCache

租户上下文

前端

Vue Draggable Vue项目的拖拉拽库

Vue.Draggable是一款基于Sortable.js实现的vue拖拽插件。支持移动设备、拖拽和选择文本、智能滚动,可以在不同列表间拖拽、不依赖jQuery为基础、vue 2过渡动画兼容、支持撤销操作,总之是一款非常优秀的vue拖拽组件。

其他

系统参数

运行期应用JVM参数:

-Dmodule.refreshAllOnStartup=true
-Damino.homeE:\beecode\new-platform\nysf_amino_home
-Dbcp.type.constraint.numeric.ignoreFloatType=true
-Dbcp.type.constraint.allowDefaultIgnore=true
-Dbcp.type.constraint.ingoreIncompatibleConstraints=true
-Dcom.beecode.dev.alwaysCreateProductDevComponents=true
-Dspring.profiles.active=dev

DevProperties

运行期前端调用后端接口,刷新PDC 元文件的缓存,用于控制是否总是刷新,如果设置为false,则直接返回缓存;

com.beecode.dev.alwaysCreateProductDevComponents=true

第三方技术组件

技术组件 描述
Hibernate ORM 框架 多租户
Tuplizer
npsql
SpringBoot
Javassist Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类, 并在 JVM 加载时修改类文件。
antlr 语法解析库 解析公式表达式语法
dom4j 解析XML文本 解析XML格式元数据文件时使用
jackson 解析JSON文本 解析JSON格式元数据文件时使用
caffeine 进程内缓存,单据实例数据存放在这里
jbpm 工作流引擎

命名规则

命名 示例 说明
BeanFactory MetadataBeanFactory
FactoryBean
…Mech AbstractMetadataMech
BillMech
…Definition MetadataBeanDefinition
…Registry ActionRegistry 注册器
…Support MetadataBeanSupport
ApplicationObjectSupport
…Aware
…Context SecurityContext
MetadataContext
Configurable…
…Container

设计原则

接口分离原则

参考:

public abstract class MetadataBeanSupport
		implements MetadataBean, InitializingMetadataBean, CompletingMetadataBean, DisposableMetadataBean {
			
		}

模版方法模式:

public abstract class MetadataBeanSupport{

	public final void init(InitContext context, Metadata metadata) {
		//...
		initBean(context, metadata);
	}
	
	protected void initBean(InitContext context, Metadata metadata) {
	}
}

设计模式

单例

AbstractValueType 的所有子类,值类型

  • BooleanTypeImpl
  • DateTypeImpl
  • UuidTypeImpl
  • IntTypeImpl

工厂

包装器

public class DataObjectWrapper extends AbstractKObject {}

技巧

自定义配置类

Hibernate配置类 HibernateStoreAutoConfiguration

将系统用的事务管理器、会话工厂、操作模板与普通业务用的区分开;

SystemHibernateAutoConfiguration

/**
 * 系统用的事务管理器bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_SYSTEM_TX_MANAGER
 */
String BEAN_SYSTEM_TX_MANAGER = "np.system.TxManager";
/**
 * 普通业务用的事务管理器bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_PRIMARY_TX_MANAGER
 */
String BEAN_PRIMARY_TX_MANAGER = "np.primary.TxManager";

/**
 * 系统用的Hibernate会话工厂bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_SYSTEM_HIBERNATE_SESSION_FACTORY
 */
String BEAN_SYSTEM_HIBERNATE_SESSION_FACTORY = "np.system.hibernate.SessionFactory";
/**
 * 系统用的Hibernate操作模板bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_SYSTEM_HIBERNATE_TEMPLATE
 */
String BEAN_SYSTEM_HIBERNATE_TEMPLATE = "np.system.hibernate.HibernateTemplate";
/**
 * 系统用的Hibernate事务管理器bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_SYSTEM_HIBERNATE_TX_MANAGER
 */
String BEAN_SYSTEM_HIBERNATE_TX_MANAGER = "np.system.hibernate.TxManager";

String BEAN_NP_PRIMARY_HIBERNATE_SESSION_FACTORY = "np.primary.hibernate.SessionFactory";

/**
 * 普通业务用的Hibernate会话工厂bean
 */
String BEAN_PRIMARY_HIBERNATE_SESSION_FACTORY = "beecode.primary.hibernate.SessionFactory";
/**
 * 普通业务用的Hibernate操作模板bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_PRIMARY_HIBERNATE_TEMPLATE
 */
String BEAN_PRIMARY_HIBERNATE_TEMPLATE = "np.primary.hibernate.HibernateTemplate";
/**
 * 普通业务用的Hibernate事务管理器bean
 * @see com.jiuqi.np.tenant.spring.TenantConstants#BEAN_PRIMARY_HIBERNATE_TX_MANAGER
 */
String BEAN_PRIMARY_HIBERNATE_TX_MANAGER = "np.primary.hibernate.TxManager";

​ 系统元数据中的hbm文件是通过硬编码的方式引入的,具体位置 MkHibernateSessionFactoryBean.createBuilder, 不要再额外配置hbm文件扫描了。

​ 因为表结构热更新使用过重新构建 SessionFactory 来实现的,所以 hibernate.hbm2ddl.auto: update 配置一定要打开,否则不会热更新。

package org.springframework.orm.hibernate5;

public class LocalSessionFactoryBean extends HibernateExceptionTranslator
		implements FactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
}

MetadataMech

MetadataMech 接口和 Spring 的 FactoryBean 接口非常相似。用于生成某种类型的Bean;

升级计划

完善

目前系统启动完成后,必须由运行期前端调用运行期后端的 POST /product/components 接口,才能保证设计器前端登录的时候,调用 GET /product/components 接口不报错。

所以,应该保证设计期前端第一次登录时,就自动调用POST 接口,初始化元数据。

方案:暂无

升级

升级计划:

  • np Springboot升级到2.7、gradle 升级到6.8
  • amino Springboot升级到2.7、gradle 升级到6.8
  • bap Springboot升级到2.7、gradle 升级到6.8
  • 运行期后端 Springboot升级到2.7、gradle 升级到6.8
  • 构建中心支持前后端合并部署,提高易用性;
  • 构建中心支持 【权限项】元数据类型的维护
  • 构建中心支持 【功能树】元数据类型的维护
  • 支持 【工作流】元数据类型的维护
  • 平台文档完善:持续工作
  • 平台升级到JDK11;
  • 平台统一升级到Springboot3.x / JDK17

修改记录

np Springboot升级到2.7

1. 依赖修改  com.beecode:hibernate-core  改为 org.hibernate:hibernate-core 
2,将 JdbcOperationsDependsOnPostProcessor 已废弃,改为 @DependsOn 注解方式
3. 工程中如果缺少 javax.validator 包,   引入 hibernate_validator 依赖即可;  lib.hibernate_validator
4. MongoClient 的创建方式发生变化;

README

作者:银法王

修改记录:

​ 2023-03-08 第一次修订

​ 2023-05-30 完善

参考:

​ 公司自研低代码平台

鲁班-58房产低代码平台设计与实践

干货 | 携程后台低代码平台的探究与实践

JSqlParser

​ [1]郭文学.Web应用快速开发工具设计与实现[D].山东大学,2022.


低代码平台系统设计
http://jackpot-lang.online/2023/03/08/系统技术架构设计/低代码平台系统设计/
作者
Jackpot
发布于
2023年3月8日
许可协议