1. 前言
前边工作流引擎中提到表单,在工作中也做过类似的功能,这里对表单系统进行梳理。
表单系统的特点如下:
从表层来看,是表单,背后却是表格与计算。在之前的设计中,采用json化方式来描述表单;提交数据后,将数据先进行存储,然后交给transform,对数据进行转换,其中会链表查询一些数据,将转换的结构存到NoSQL(ES)中;查询时,通过配置化的方式,从NoSQL中查询、统计数据。
下边先来看看这个过程,然后再找找开源的实现,看看它们是怎么做的。
formaily
2. 表单
数据
type: 类型
field: 前端生成的id
label: 前端的字段名,后端用于column
validator:前端用于表单验证,后端用于创建tabel约束
widget:用于控制前端显示的
meta:用于控制数据,包括:fomular、depend
再细节一些,需要对各种表单项进行封装
[
{
"type": "String",
"field": "_field_1617951999062",
"label": "姓名",
"validator": {
"nullable": false, // required: true
"unique": true,
"primary": true
},
"widget": {
"hint": "请输入姓名",
"width": "12"
}
},
{
"type": "Number",
"field": "_field_1617951999062",
"label": "年龄",
"validator": {
"nullable": false, // required: true
"max": 999,
"min": 0
},
"widget": {
"width": "12",
}
},
{
"type": "Date",
"field": "_field_1617951999062",
"label": "出生日期",
"validator": {
"nullable": true,
},
"widget": {
"width": "12",
"format": "yyyy-MM-dd"
}
},
{
"type": "Select",
"field": "_field_1617951999062",
"label": "性别",
"validator": {
"nullable": false,
},
"widget": {
"width": "12",
"items": [
{"text" : "男", "value": "男", "selected": true},
{"text" : "女", "value": "女"}
]
},
"meta": {
"type": "formular",
"formula": "$_field_1617951999024#+$_field_1617951999062#"
}
}
]
前端提交表单设置(相当于表头),后端存库,一般保存在关系型数据库中。
这里有两个难点:
-
公式
公式是本表单内的数字字段之间的依赖关系,这个主要是对每一个显示字段指定唯一的field即可。
"meta": {
"type": "formular",
"formula": "$_field_1617951999024#+$_field_1617951999062#"
}
-
数据关联
数据关联是两个表之间的数据依赖。
简单的关系是直接选择,既制定另外一个表单的字段,本表单字段为该字段,此种方式用meta即可。这种像是一种外键。
...
"meta": {
"type": "depend",
"form": "5c6fbafed52b580aa3fb262c",
"targetField": "_field_1550826244331"
}
复杂是通过通过选择另外一个表单的字段(一般为主键),将这个表中其他的几个字段都关联出来。这里的依然是一个字段对一个字段的带出。Select targetField from 表1 Where 表1.主键字段 =本表.外键字段
...
"meta": {
"type": "depend",
"form": "5c6fbafed52b580aa3fb262c", // 表1
"targetField": "_field_1550826244331", // 目标字段1
"primaryField": "_field_1617951998390", // 表1的主键字段
"foreignField": "_field_1550826244370" // 本表的外键
}
操作
-
添加记录
需要指明表单id,后端获取该表单,对数据进行验证,通过后,将数据存储在数据库中(可以是pg或者mongodb)
-
修改记录
需要指明表单id,与数据的id,前端需要提供所有的数据(包括修改与未修改)。根据表单id获取表单,对修改的数据进行验证,通过后,更新到数据库。
-
删除记录
提供表单id与数据id,根据表单id找到对应的数据表,然后删除数据
存储方案
存储方案主要有2种:关系型数据库方式、非关系型方式
关系型数据库需要根据表单结构生成schema,创建表单存储表;
非关系型数据库存不需要生成表,存储文档即可;
下边来看看这两种方案,以pgsql与mongodb为代表,之前实现中用的是elasticsearch,但elasticsearch运行起来有点费内存,感觉用mongodb更好一些。
pgsql
这种方案跟LOS目前的方案相同,根据查询的结构,生成对应的语句,然后执行,返回结果。
实现上:
-
标准化查询接口,即上述的json格式;
-
json的解析器
解析json结构,调用相应的生成器,生成sql
接口比较标准的化,可以直接调用sql生成器即可;
-
sql的生成器
根据不同的参数,生成对应的sql语句
mongodb
2018-7-18mongo基础
mongo使用场景
谈谈MongoDB适用领域
MySQL Vs MongoDB aggregation performance
如下:
好处 | 坏处 |
不用创建Schema,对表单修改时比较优化 | 内存占用比较大; |
分库分表会更简单一些 | million级别,聚合函数在会比mysql费1倍左右的时间 |
mongo对node.js更友好一些,可以借鉴mongo的语法来定义接口 | 现有的开发用的是sql方式,开发更友好一些 |
方案:用mongodb的语法,重新规整一下接口,再将该接口转义成sql,采用关系型数据库来实现
这样若以后采用mongo,也可以很快的转义成mongo的语法
3. 统计
统计基本操作包括过滤、分组、聚合函数、排序,难点操作是链表。
既可以用sql,也可以用nosql(es、mongon等)的方式,下边来看看。
查询字段
{
"type": "project",
"arg": ["_field_xxx1", "_field_xxx2"]
}
过滤
{
"type": "match",
"arg":[
{
"filed": "_field_xxx",
"method": {
"eq": "xxxx" // 或者"gt"、"lt"
}
},
{
"filed": "_field_xxx",
"method": {
"eq": "xxxx" // 或者"gt"、"lt"
}
}
]
}
以上为简单方式,功能之间为and。复杂一点的如下图所示:
{
"type": "match",
"arg":{
"method": "or",
"arg": [
{
"filed": "_field_xxx",
"method": {
"eq": "xxxx" // 或者"gt"、"lt"
}
},
{
"filed": "_field_xxx",
"method": {
"eq": "xxxx" // 或者"gt"、"lt"
}
}
]
}
可以先实现简单的查询
分组聚合
{
"type": "group",
"arg": ["_field_xxx1", "_field_xxx2"],
"aggregation": {
"type": "sum",
"field": "_field_xxx3"
}
SELECT SUM(_field_xxx3) AS total FROM _formxxx GROUP BY _field_xxx1, _field_xxx2
排序
{
"type": "sort",
"arg" : [
{
"field": "_field_xxx1",
"order" : 1
}
]
}
ASC=1, DESC=0
链表 join
链表有2种方式:
-
生成语句方式
从语句角度上,链表需要提供join 表1 on 表1.字段=本表.字段
3个属性
好处是:配置化实现,不需要定制;
坏处是:数据过大或语句过复杂会存在性能问题
-
中间表方式
可以通过生成中间表暂存一些结果来加速查询,在pms_record中使用的就是该方式
好处是:性能会不错
坏处是:需要定制中间表,中间表还占存储空间
{
"type": "join",
"arg": {
"form": "xxxx",
"primaryField": "_field_xxx",
"foreignField": "_field_yyy"
}
}
4. 资料
网上资料大多只介绍了表单,
前端vue-form-makingstar更多一些,通过拖动实现表单的设计;
这篇DynamicForm介绍了动态表单的演化
动态表单设计这里对存储有所介绍,他这里将表单的结构存放在MySQL中,将数据存放到MongoDB中,跟我的做法类似;