表单系统梳理

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#"
        }
    }
]

前端提交表单设置(相当于表头),后端存库,一般保存在关系型数据库中。

这里有两个难点:

  1. 公式

    公式是本表单内的数字字段之间的依赖关系,这个主要是对每一个显示字段指定唯一的field即可。

    "meta": {
        "type": "formular",
      	"formula": "$_field_1617951999024#+$_field_1617951999062#"
    }
    
  2. 数据关联

    数据关联是两个表之间的数据依赖。

    简单的关系是直接选择,既制定另外一个表单的字段,本表单字段为该字段,此种方式用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目前的方案相同,根据查询的结构,生成对应的语句,然后执行,返回结果。

实现上:

  1. 标准化查询接口,即上述的json格式;

  2. json的解析器

    解析json结构,调用相应的生成器,生成sql

    接口比较标准的化,可以直接调用sql生成器即可;

  3. 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种方式:

  1. 生成语句方式

    从语句角度上,链表需要提供join 表1 on 表1.字段=本表.字段3个属性

    好处是:配置化实现,不需要定制;

    坏处是:数据过大或语句过复杂会存在性能问题

  2. 中间表方式

    可以通过生成中间表暂存一些结果来加速查询,在pms_record中使用的就是该方式

    好处是:性能会不错

    坏处是:需要定制中间表,中间表还占存储空间

{
    "type": "join",
    "arg": {
        "form": "xxxx",
        "primaryField": "_field_xxx",
        "foreignField": "_field_yyy"
    }
}

4. 资料

网上资料大多只介绍了表单,

前端vue-form-makingstar更多一些,通过拖动实现表单的设计;

这篇DynamicForm介绍了动态表单的演化

DynamicForm模块架构图 DynamicForm流程图

动态表单设计这里对存储有所介绍,他这里将表单的结构存放在MySQL中,将数据存放到MongoDB中,跟我的做法类似;

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×