一,背景
随着低代码概念的火热,相关的技术及产品也是层出不穷,不管是老牌行业软件厂商还是开放平台厂商,不论是互联网行业企业SAAS软件新动向还是新兴的低代码创新产品服务,都在第一时间打出了低代码这张牌。各个平台虽然各有优势,但大多又是自成体系,真正在企业方面进行选择时却一时难以抉择。对于低代码平台的功能评价,以及各平台组件间的互联互通则成为了市场上迫切需求。在刚刚过去的2022年,在平台互联互通的方面,阿里在第二季度推出开源引擎 “LowCodeEngin”,国家队信通院也应市场需求在第四季度推出了《低代码开发平台通用技术要求》,针对低代码相关概念以及功能点新型了进一步的规范和梳理。在全面开放的大背景下,CodeBee团队,推出了基于开源LGPL协议 低代码引擎(LowCodeEngine)。
开源地址:wenzhang/ocstudio
在线演示地址: cluster.raddev.cn:9090/RAD/DSMdsm/…
二,产品组成
低代码引擎,由界面设计器、OneCode通码框架以及,DSM领域建模工具 三部分支撑体系相互支撑的部分来组成,通过开放标准的组件协议完成相互继承支持。
(1) 视图设计器
引擎设计器,采用的是拖拽引擎+插件的构造模型,用户可以通过开放的低代码协议编写插件。支持JS和JAVA两种扩展语言。样式构建提供了标准CSS3编辑器,支持事件动作以及函数动态扩展。支持自定义函数库扩展,支持阿里字体图片等资源库。
(2)OneCode通码编辑器
OneCode,是一款为低代码语言定制的统一语法体系,采用Java语言作为原生语言,运行在JVM环境中,用户可以通过Java语言与低代码应用进行交互,也可以通过Java语言完成引擎插件,调用代码引擎完成编译部署应用。
(3)DSMEngine领域建模
DSMEngine 是独立于设计器的OneCode建模工具,平台采用领域建模模型,支持仓储管理、聚合应用,以及CodeFactory输出为OneCode代码编译输出。
DSM模型支持三种建模模式:
(1)CodeFrist
代码优先模式通过Java语言 OneCode 模式原生撰写。
(2)ViewFrist
视图优先通过视图引擎拖拽完成前期的交互模型,反向完成DSM模型。
(3)ModuleFrist
模型优先通过数据库,微服务接口等模式,构建基础模型。
(4)DSM逆向转换
通过不同方式完成的DSM模型,可以通过OneCode 在视图、Code 、以及Module 三种方式之间自由切换,利用相关工具完成仿真调试以及部署运行。
(5)DSM第三方语言转换
DSM出码模块采用了独立的模板架构,除了可以以OneCode形式存在,还可以支持独立的出码模块定制独立的第三方语言模型输出。
三,技术原理
(1)顶层设计
OneCode由三块自成体系的可独立部署运行的部分组成。前端引擎负责界面建模并按低代码协议协议生成标准JSON,中后台OneCode通过读取标准JSON协议,完成后端的视图建模,合并DSM后端服务建模系统,完成完整的后端服务建模应用,通过代码工程完成前后端一体的出码应用。
(1)JDSCloud
JDSCloud是OneCode的协同支撑系统,除了常规的资源代码空间管理外,提供了独立的沙箱运行环境。为OneCode 提供工程化的仿真版本Ops等服务。
(2) 可视化建模过程
用户通过,拖拽完成页面建模序列化为按标准协议序列化JSON文件,后端OneCode服务支撑系统解析JSON文件并混合DSM建模信息以及后端服务逻辑后,通过混合编译,通过代码工厂指定出码模板,完成前后端一体的编译文件。
(3)OneCode
OneCode 本身基于JAVA语言体系,是在Java Spring 注解基础上的一套扩展子集,混合编译引擎器通过扩展注解构建完整的Domain模型,通过读取标准Spring 注解完成普通Web数据交付及调度过程,通过Domin域模型动态渲染JS文件输出为JSON交付给前端引擎构建页面。
四,产品优势
(1)开放协议,开源实现提升标准化能力
OneCode平台在设计上将前端组件的设计上即采用了开放式设计模型及及存储通讯标准。这将在很大程度上为各家低代码平台组件互联互通提供便利,在设计上实现通用通行。避免形成应用孤岛。解决用户被绑定在特定平台的忧虑。
(2)基于DSM领域模型的双向建模
OneCode除了在前端实现了标准化组件定义外,还额外提供了后端建模的工具DSM,并通过领域模型将二者打通。这样在前端组件建模时便可以直接调用后端服务模型完成数据部分API构建。而DSM模型工具也可以在后端建模时直接读取前端组件属性,打通前端动作与后端服务的通讯能力。
视图设计器通过,后端模型绑定插件快速选定后端Agg聚合服务模型接口,配置页面快速绑定前后台交互
后端DSM建模通过视图模型扩展直接修改操作,前端组件模型二者模型的打通大幅降低了低代码平台的劳动强度,同时通过针对模型的建模干预API,在模型建立重构的方面可以从更深的层次上建立业务模型的构建能力。
(3)混编检测智能检测排错
传统低代码平台基本上都是完全建立在JS的模型下,在初期建模时结构还算清晰但经过稍有点复杂的逻辑,构建时代码的冗余度以及结构就会变得混乱,特别是页跨页面操作或者完成前后台数据交互时。由于其脚本语言的特点无法完成实时校验,只能运行期测试才能发现问题。采用低代码构建的页面往往只是由于页面中做了一些简单的组件增删或者属性样式就该就会造成不可预期的结果,这大大降低了代码的可维护度。OneCode所构建的领域模型则很好的解决了这一问题,在前后端任意模型发生变化时即可调用混合编译,将页面间的连接关系以及前后台的数据关系进行校验通知。在预编译中提升整体的编译能力。
五,设计器引擎介绍
(1)功能概览
(2)设计器布局
设计器引擎是低代码引擎前端的SDK,面向开发人员,他本身不是一套可以适应所有人的低代码平台,而是技术开发人员可以通过扩展插件,周边生态,完成自身业务的定制,实现协同办公,CRM客户管理、物联网平台等通过低代码能力赋能业务系统
(3)物料库
"物料":低代码引擎的核心目的之一是建设跨行业的低代码框架,而每个行业由于其应用的领域不同,使用的人员以及方法方式不同,在一些底层组件方面会有会有加大差距。比如:政府业务中会大量使用的非规则表单元素,企业应用中各个行业自有的图标体系,物联网行业大量的设备图标图片以及实时联网图。
(4)组件库
组件定义:可以用于低代码平台的组件,包含了搭建体验增强配置,可以在设计器中 进行拖拽、配置等操作。有两种分类方式:按照场景可以分为基础组件、业务组件、图 表组件、布局组件和复合组件等。通常用户可以自主完成相关设定,并根据业务特点在视图引擎中进行自行扩展(后续章节中会演示实际注册示例)
组件通常是一组完成特定功能的可交互组件,根据不同的引擎模式,在引擎中完成加载渲染配置。配置示例
Code转换
组件调试导入
(5)支撑管理
公共资源导入
(6)样式体系
(1)DOM树透视样式盒
(2)DOM树透视
配图示例代码
{
"alias":"BuildTreeTreeView",
"key":"xui.UI.TreeView",
"host":this,
"properties":{
"name":"BuildTreeTreeGrid",
"items":[
{
"borderType":"none",
"caption":"JAVA树",
"dynDestory":false,
"hidden":false,
"id":"getBuildTree",
"imageClass":"bpmfont bpmgongzuoliuxitongpeizhi",
"tagVar":{ }
}
],
"iniFold":false,
"dynDestory":true
},
"CS":{
"KEY":{
"color":"#000000",
"font-weight":"lighter",
"border-radius":"0px 2px 0px 0px"
},
"BAR":{
"font-family":"tahoma,geneva,sans-serif"
}
}
}
(3)动态样式盒
代码配置示例
{
"alias":"xui_ui_cssbox1",
"key":"xui.UI.CSSBox",
"host":this,
"properties":{
"className":"xui-css-ame",
"normalStatus":{
"color":"#eeeeee",
"border-radius":"6px",
"box-shadow":"inset 0px 1px 0px #87C1DD",
"text-shadow":"0 1px 0 #297192",
"$gradient":{
"stops":[
{
"pos":"0%",
"clr":"#4BA3CC"
},
{
"pos":"70%",
"clr":"#3289B2"
}
],
"type":"linear",
"orient":"T"
},
"cursor":"pointer",
"border-top":"solid #3899C6 1px",
"border-right":"solid #3899C6 1px",
"border-bottom":"solid #3899C6 1px",
"border-left":"solid #3899C6 1px"
},
"hoverStatus":{
"border-radius":"0px 3px 0px 0px"
}
}
}
(7)事件框架
配置代码示例:
{
"alias":"BuildTreeTreeView",
"key":"xui.UI.TreeView",
"host":this,
"properties":{
"name":"BuildTreeTreeGrid",
"items":[
{
"borderType":"none",
"caption":"JAVA树",
"dynDestory":false,
"hidden":false,
"id":"getBuildTree",
"imageClass":"bpmfont bpmgongzuoliuxitongpeizhi",
"tagVar":{ }
}
],
"iniFold":false,
"dynDestory":true
},
"events":{
//获取数据
"onGetContent":{
"actions":[
{
"args":[
"{page.ReloadChild.setQueryData()}",
null,
null,
"{args[1].tagVar}",
""
],
"desc":"设置扩展参数",
"method":"setQueryData",
"redirection":"other:callback:call",
"target":"ReloadChild",
"type":"control"
}
]
},
//数据项选择
"onItemSelected":{
"actions":[
{
"args":[
"{args[1].id}"
],
"conditions":[
{
"symbol":"non-empty",
"right":"",
"conditionId":"nonempty{args[1].className}",
"left":"{args[1].className}"
}
],
"desc":"删除存在页",
"method":"removeItems",
"target":"BuildTreeTab",
"type":"control"
}
]
}
}
}
(8)动作调用
功能概览
配置实例代码:
{
"args":[
"{page.ReloadChild.setQueryData()}",
null,
null,
"{args[1].tagVar}",
""
],
"desc":"设置扩展参数",
"method":"setQueryData",
"redirection":"other:callback:call",
"target":"ReloadChild",
"type":"control"
},
{
"args":[
"{page.ReloadChild.invoke()}",
"temp",
null,
"{args[2]}"
],
"desc":"子节点装载",
"method":"invoke",
"redirection":"other:callback:call",
"return":false,
"target":"ReloadChild",
"type":"control"
}
]
}
(9)插件体系
插件是嵌入到设计器的内置管理功能,不同于业务组件,插件更多的是系统极的扩展功能。在实际应用中也比较常见,如系统运行期我们要根据用户不同显示不同内容数据,这就需要权限插件来完成,而业务用户在使用过程也会涉及到大量的业务和数据的流转功能而这些功能则需要动态的来管理页面的属性,甚至动态生成注入页面。这就需要流程插件来辅助完成。 在实际开发过程中特别是真实项目的工程开发时,我们往往要针对工程方面的进行很多的宏操作比如批量的修改特定组件样式,按特定条件检索复制组件特性,自动添加动作等等。这些都需要类似的宏插件来完成,OneCode 在整合后端运行以及服务部署方面也是按插件体系来规范的。分别针对,DSM建模提供了DSM插件,发布管理及运行提供了OPS插件, API整合方面提供了代理服务器插件,系统插件部分采用全开源方式共有需要的用户自行修改方便用户后期可以参照插件体系来修改自身的插件体系。
六,OneCode一码通
OneCode 本身基于JAVA语言体系,是在Java Spring 注解基础上的一套扩展子集,同时为不同注解子集提供了与其配套的解析引擎。将数据模型全面配置化统一化,实现了DDD领域建模中真正意义上的统一语言环境统一当前环境。只需要基础的代码环境就可无缝切换各种运行环境,在用户应用上实现多端合一服务,开发过程中真正达到,自由切换设计、代码、应用模式。
(1)示例展示
(2)完整模块OneCode
@Controller
@RequestMapping("/admin/org/person/")
@MethodChinaName(cname = "人员管理", imageClass = "spafont spa-icon-login")
@Aggregation(sourceClass = PersonService.class)
public class PersonAPI {
@RequestMapping(method = RequestMethod.POST, value = "Persons")
@GridViewAnnotation()
@ModuleAnnotation( caption = "人员列表")
@APIEventAnnotation(autoRun = true, bindMenu = {CustomMenuItem.reload})
@ResponseBody
public ListResultModel<List> getPersons(String orgId) {
ListResultModel<List> resultModel = new ListResultModel<List>();
List personList = new ArrayList<>();
try {
personList = getService().getPersons(orgId);
resultModel = PageUtil.getDefaultPageList(personList, PersonGridView.class);
} catch (Exception e) {
e.printStackTrace();
}
return resultModel;
}
@MethodChinaName(cname = "人员信息")
@RequestMapping(method = RequestMethod.POST, value = "PersonInfo")
@NavGroupViewAnnotation()
@APIEventAnnotation(callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}, bindMenu = {CustomMenuItem.editor})
@DialogAnnotation
@ModuleAnnotation(caption = "编辑人员信息", width = "800", height = "550")
@ResponseBody
public ResultModel getPersonInfo(String personId) {
ResultModel resultModel = new ResultModel();
return resultModel;
}
@MethodChinaName(cname = "添加人员")
@RequestMapping(method = RequestMethod.POST, value = "AddPersonView")
@FormViewAnnotation
@APIEventAnnotation(bindMenu = {CustomMenuItem.add}, autoRun = true)
@Disabled
@ModuleAnnotation( caption = "添加人员信息", width = "370", height = "260")
@ResponseBody
public ResultModel AddPerson(String orgId) {
ResultModel resultModel = new ResultModel();
CtPerson person = new CtPerson();
person.setOrgId(orgId);
resultModel.setData(new AddPerson(person));
return resultModel;
}
@MethodChinaName(cname = "保存成员信息")
@RequestMapping(value = {"savePerson"}, method = {RequestMethod.GET, RequestMethod.POST})
@APIEventAnnotation(callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}, bindMenu = CustomMenuItem.save)
public @ResponseBody
ResultModel savePerson(@RequestBody CtPerson person) {
ResultModel userStatusInfo = new ResultModel();
getService().savePerson(person);
return userStatusInfo;
}
@MethodChinaName(cname = "删除人员")
@RequestMapping(value = {"delPerson"}, method = {RequestMethod.GET, RequestMethod.POST})
@APIEventAnnotation(callback = {CustomCallBack.Reload, CustomCallBack.ReloadParent}, bindMenu = CustomMenuItem.delete)
public @ResponseBody
ResultModel delPerson(String iD) {
ResultModel userStatusInfo = new ResultModel();
getService().delPerson(iD);
return userStatusInfo;
}
PersonService getService() {
return EsbUtil.parExpression(PersonService.class);
}
}
@PageBar //
@GridAnnotation(event = CustomGridEvent.editor,
customService = PersonService.class,
customMenu = {GridMenu.Add, GridMenu.Delete, GridMenu.Reload})
public class PersonGridView {
@CustomAnnotation(pid = true, hidden = true)
String orgId;
@CustomAnnotation(pid = true, hidden = true)
String roleId;
@CustomAnnotation(uid = true, hidden = true)
String iD;
@CustomAnnotation(caption = "用户名称", required = true)
String name;
@CustomAnnotation(caption = "账户信息", required = true)
String account;
@CustomAnnotation(caption = "邮箱")
String email;
@InputAnnotation(inputType = InputType.password)
@CustomAnnotation(caption = "密码", required = true)
String password;
@CustomAnnotation(caption = "手机")
String mobile;
@CustomAnnotation(caption = "部门名称")
String orgName;
public PersonGridView(Person person) {
this.iD = person.getID();
this.orgId = person.getOrgId();
this.name = person.getName();
this.account = person.getAccount();
this.password = person.getPassword();
this.mobile = person.getMobile();
this.email = person.getEmail();
Org org = null;
try {
org = OrgManagerFactory.getOrgManager().getOrgByID(person.getOrgId());
this.orgName = org.getName();
} catch (OrgNotFoundException e) {
e.printStackTrace();
}
}
}
@BottomBarMenu
@FormAnnotation(bottombarMenu = {CustomFormMenu.Save, CustomFormMenu.Close}, customService = PersonService.class, col = 1)
public class AddPerson {
@CustomAnnotation(uid = true, hidden = true)
String iD;
@CustomAnnotation(caption = "用户名称", required = true)
String name;
@CustomAnnotation(pid = true, hidden = true)
String orgId;
@CustomAnnotation(pid = true, hidden = true)
String roleId;
@CustomAnnotation(caption = "账户信息", required = true)
String account;
@CustomAnnotation(caption = "邮箱")
String email;
@InputAnnotation(inputType= InputType.password)
@CustomAnnotation(caption = "密码", required = true)
String password;
@CustomAnnotation(caption = "手机")
String mobile;
public AddPerson(Person person) {
this.iD = person.getID();
this.orgId = person.getOrgId();
this.name = person.getName();
this.account = person.getAccount();
this.password = person.getPassword();
this.mobile = person.getMobile();
this.email = person.getEmail();
}
}
(七)DSM建模工具
DSM建模,百度百科是这样定义的:特定域建模(Domain-specific modeling,DSM),是一种设计和开发系统(如电脑软件)的软件工程方法学。它系统使用图形化特定域语言(DSL),表现系统的各个方面。DSM的语言倾向于支持比通用建模语言更高级别的抽象,因此需要较少的努力和更少的底层细节来描述特定系统。低代码技术应用可以通过提供更强的工具,提升程序员的代码效率。但其本质上也是一种特定场景下的软件描述方法,这个层面上低码技术和DSM思想是有其相通相同之处的,产品在设计之初就将DSM建模语言的构建以及工具支撑作为了底层支撑设计,将应用中积累的建模应用采用DSM的思想进行重构整合在底层打通。我们将现有的资源类的工具,统一到仓库应用中,包括统一的物料库导入,统一的数据源(数据库,外部存储)管理。并通代码工厂的辅助构建统一到Contenxt(OneCode)的当前环境技术模型中。在各个特定的业务模型中,完成独立的聚合实体整合,以及相应的服务管理,并为相关的方法模型透视管理服务。在OneCode 的基础之上,摆脱传统的 代码模板与生成机制。实现代码向模型的逆向能力。将DSM设计能贯穿整个项目开发实施管理过程,打造结余真实的代码之上的建模语言。
(1)仓储建模
(2)聚合应用
(3)视图工厂
(4)支撑域
(5)流程建模
八,应用集成
(1)内核最小集合
集成(JS离线应用)设计器内核完全采用JS脚本完成,是独立的前端框架。内核版本包括:RAD 页面设计编辑器,xui运行运行脚本两部分。
最小集合仅包含,页面设计及器以及插件框架。
设计界面集成:
下载开源包后,运行debug.html 即可打开编辑器。
集成到自由应用时只需要,引入两个关键的js lib包即可集成到自有的应用。
运行支撑环境集成:打开:
设计完成的文件再应用环境中引入如下代码即可
(2)团队协作版
团队协作版是独立的服务器部署版,在官网上申请账号后,可以在独立的服务器上运行。启动后通过浏览器访问 http://demoserver:83 用管理员 sysadmin 登录:
首次登录 会进入默认的工程配置界面
配置工程
关联API
设定团队管理员