目录
前言
一、JSON Schema 规范
二、schema字段描述
三、JSON Schema 应用
一、业务描述
二、模版数据表设计
三、规则表设计
四、构造Groovy 脚本参数Map
五、执行Groovy 脚本
前言
Formily 提供了 JSON Schema、JSX Schema、纯 JSX 三种开发模式。由于 JSON 可以序列化保存到数据库中,所以 JSON Schema 的方式非常适合后端动态渲染表单,前端完全不需要维护 schema,只需利用 Formliy 提供的 SchemaForm 来渲染后端返回的 schema 即可。我们仅需通过 Form Builder 或者 Page Designer 之类的工具来输出 JSON Schema,然后交给 SchemaForm 或者 PageEngine 之类的组件来渲染。
一、JSON Schema 规范
JSON Schema 是一个社区推动的 JSON 文件协议,用于规范 JSON 文件内容。它与平台无关,可以描述任意复杂的数据结构,相比 XML,JSON 的描述格式更加紧凑,可读性更好。JSON Schema 在 JSON 的格式上,加入了一些列的标准化属性,用于描述结构化数据。Formily 遵循 JSON Schema 使用最广泛的[draft-07 标准](Specification Links
我们可以借助 ajv 这类 JSON Schema 验证工具,来认识不同 Schema 规范的区别,详情可参考 [draft-07 (and draft-06)](Ajv JSON schema validator)。
用 JSON Schema 来描述表单适合于低代码或者数据中台的快速开发。前端不需要维护 schema,schema 可以存在后端,随意分发动态渲染。
二、schema字段描述
演示地址:https://xrender.fun/schema-builder
{
"bsonType": "object", // 固定节点
"description": "表的描述",
"required": [], // 必填字段,比如name,age
"permission": {
"read": false, // 前端非admin的读取记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"create": false, // 前端非admin的新增记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"update": false, // 前端非admin的更新记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"delete": false, // 前端非admin的删除记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"count": false // 前端非admin的求数权限控制。默认值是true,即可以不写。可以简单的true/false,也可以写表达式
// 对数据进行数量统计时(包括count方法、及groupField内的count操作)均会同时触发表级的count权限及read权限
},
"properties": { // 表的字段清单
"_id": {
"description": "ID,系统自动生成"
},
"other": { // 字段名称,每个表都会带有_id字段
"bsonType": "", // 字段类型,可选值: string | password | int | double | bool | date | timestamp | object | file | array
"arrayType": "file", // 指定字段类型为数时,其元素项类型,bsonType="array" 时有效。
"title": "", //<string> 标题,开发者维护时自用。在schema2code生成前端表单代码时,默认用于表单项前面的label
"description": "", //<string> 描述,开发者维护时自用。在生成前端表单代码时,如果字段未设置componentForEdit,且字段被渲染为input,那么input的placehold将默认为本描述
"defaultValue": "", //<string | object>默认值
"forceDefaultValue": "", //<string | object> 强制默认值,不可通过clientDB的代码修改,常用于存放用户id、时间、客户端ip等固定值。
"required": [], //<array> 是否必填。支持填写必填的下级字段名称。required可以在表级的描述出现,约定该表有哪些字段必填。也可以在某个字段中出现,如果该字段是一个json对象,可以对这个json中的哪些字段必填进行描述。
"enum": [], //<array> 字段值枚举范围,数组中至少要有一个元素,且数组内的每一个元素都是唯一的。enum最多只可以枚举500条
"enumType": "", //<string> 字段值枚举类型,可选值tree。设为tree时,代表enum里的数据为树形结构。此时schema2code可生成多级级联选择组件
"fileMediaType": "", //<string> 文件类型,bsonType="file" 时有效,可选值 all|image|video 默认值为all,表示所有文件,image表示图片类型文件,video表示视频类型文件
"fileExtName": "", //<string>文件扩展名过滤,bsonType="file" 时有效,多个文件扩展名用 "," 分割,例如: jpg,png,
"maximum": 20, //<number>如果bsonType为数字时,可接受的最大值
"exclusiveMaximum": , //<boolean>bsonType为数字时, 是否排除 maximum
"minimum": , //<number>如果bsonType为数字时,可接受的最小值
"exclusiveMinimum", //<boolean>bsonType为数字时, 是否排除 minimum
"minLength", //<number>bsonType = String | Array时,限制字符串或数组的最小长度
"maxLength", //<number>bsonType = String | Array时,限制字符串或数组的最大长度
"trim": "both", //<boolean> bsonType = String 时,去除空白字符。可选值none|both|start|end,默认值none trim优先级高于其它驗证,它会在先去掉空格之后,再进行驗证。
"format": //枚举类型,可选值 url | email。设置字段数据格式。目前只支持这二种格式。数据不符合此格式时无法录入。
"pattern": //<string>正则表达式,比如设置手机号码正则,如果该字段值不符合手机号正则,则录入失败。 验证手机号正则:"pattern": "^\\+?[0-9-]{3,20}$"
"validateFunction": "", //<string | object>扩展校验函数名,用于校验该字段值是否符合要求
/* 值类型为:String 时表示校验函数名
值类型为object格式:
{
"name": """ //<string>校验函数名
"client": true //<boolean>默认值true。值为true时客户端也可以在生成的代码中改为自己的校验函数,此时客户端的校验仍然生效(不懂)
} */
"permission": { //<object> 字段的数据库权限,仅write、read
"write": true,
"read": true
},
"errorMessage": "", //<string|Objeect>当数据写入或更新时,校验数据合法性失败后,返回的错误提示
// 比如:
// "errorMessage": "{title}不能为空" {title} 为当前字段设置的title属性值
// 或者指定具体出错点
// "errorMessage": {
// "required": "{title}不能为空", //当required不满足时,出错提示内容
// "minLength": """ , //当minLength不满足时,出错提示内容
// "maximum": """ , //当maximum不满足时,出错提示内容
// "pattern": , //当pattern不满足时,出错提示内容
// "format": "{title}格式无效" //当format不满足时,出错提示内容
// }
"foreignKey": "", //<string>外键索引。关联字段。表示该字段的原始定义指向另一个表的某个字段,值的格式为:关联表名.字段名。关联字段定义后可用于联表查询,通过关联字段合成虚拟联表,极大的简化了联表查询的复杂度
"parentKey": "", //<string>同一个数据表内父级的字段。 用于树状(tree)数据查询
"label": "", //<string> schema2code的配置。字段标题。schema2code生成前端代码时,渲染表单项前面的label标题。如果不填,会使用title属性。适用于title不便显示在表单项前面的情况
"group": "", //<string> schema2code的配置。分组id。schema2code生成前端代码时,多个字段对应的表单项可以合并显示在一个uni-group组件中
"order": 0, //<int> schema2code的配置。表单项排序序号。
//!!!!! componentForEdit 类型为对象和数组。测试数组不成功。对象可以。~!!!!
"componentForEdit": {}, //<object | array> 生成前端编辑页面文件时(add.vue、edit.vue),使用什么组件渲染这个表单项。比如使用input输入框。格式:
/* 示例一:简单的组件
{
"name": "input", //此字段所用组件名
"props": { //设置组件属性
"placeholder": "请输入昵称" //逻辑或数值类型要加:号,比如: ":disabled": false
}
//"children": '' //<string> 子组件名。
//"childrenData": [] //<array> 子组件数据
}
示例二:带子组件的组件
{
"name": "select",
"children": "<option value=\"{value}\">{label}</option>",
"childrenData": [{"label": "中文简体", "value": "zh-cn"}]
} */
"componentForShow": [] // <object | array>生成前端展示页面时(list.vue、detail.vue),使用什么组件渲染。比如使用uni-dateformat格式化日期。
}
},
"fieldRules": [
/* 字段之间的约束关系。比如字段开始时间小于字段结束时间。也可以只校验一个字段。支持表达式
注意:这里的rule是为符合条件,而不是不满足条件
{
"rule": "end_date == null || end_date != null && create_date < end_date", // 校验规则
"errorMessage": "创建时间和结束时间不匹配", // 错误提示信息(仅在新增时生效,更新数据时不会提示此信息)
"client": false // schema2code时,当前规则是否带到前端也进行校验。目前此属性暂不生效,fieldRules不会在客户端校验数据,仅会在云端进行校验
} */
]
}
三、JSON Schema 应用
一、业务描述
在项目开发中,我们经常需要定义一些规则,根据规则匹配处理相应的业务。比如在广告投放业务中,我们有以下规则的动态组合回传用户关键行为【注册,激活,支付等转化】。
- 用户阅读时长 + 用户收益
- 广告曝光数 + 用户收益
- 用户支付金额
二、模版数据表设计
如上,根据不同条件组合回传用户的关键行为非常适合用动态表单开发。首先我们先定义模版表
字段名称 | 字段类型 | 字段描述 |
---|---|---|
id | BIGINT(20) | 主键 |
groovy_code | VARCHAR(64) | Groovy模版编码 |
groovy_name | VARCHAR(64) | Groovy模版名称 |
content | TEXT | Groovy脚本 |
schema_json | TEXT | 数据结构 |
status | TINYINT(4) | 状态 1:启用 2:停用 |
content:Groovy脚本返回Boolean 值,用于规则是否匹配成功。
用户阅读时间投放模版Groovy 脚本
import java.util.Date;
import java.util.Map;
import cn.hutool.core.convert.Convert;
import com.xinwu.shushan.core.common.ApplicationContextHelper;
import com.xinwu.shushan.launch.infra.cache.UserReadTimeCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Groovy {
private static Logger logger = LoggerFactory.getLogger("launch_book_readTime");
public Boolean match(Map<String, Object> map) {
Integer expectedReadTime = Convert.toInt(map.get("readTime"));
Integer userId = (Integer) map.get("userId");
Date date = (Date) map.get("date");
UserReadTimeCache userReadTimeCache = ApplicationContextHelper.getBean(UserReadTimeCache.class);
int actualReadBookTime = userReadTimeCache.getReadTime(date, userId);
logger.info("template-launch_book_readTime#userId={},expectedReadTime={},actualReadBookTime={}", userId, expectedReadTime, actualReadBookTime);
return actualReadBookTime >= expectedReadTime;
}
}
用户阅读时间 schema_json 数据结构:
{
"type":"object",
"properties":{
"readTime":{
"title":"阅读时长(秒)",
"type":"number",
"defaultValue":300,
"props":{
"placeholder":"请输入阅读时长"
},
"required":true,
"min":0,
"widget":"inputNumber"
}
},
"displayType":"row",
"maxWidth":"340px"
}
三、规则表设计
launch_config 投放配置表定义
字段名称 | 字段类型 | 字段描述 |
---|---|---|
id | INT(10) | 主键 |
channel_type | VARCHAR(32) | 渠道 |
product_type | INT(11) | 产品类型 |
advertiser_id | VARCHAR(64) | 广告主ID |
conversion | VARCHAR(128) | 转化 |
groovy_code | VARCHAR(64) | 模版code |
condition_json | VARCHAR(1024) | 条件[json schema 表单值] |
四、构造Groovy 脚本参数Map
private Map<String, Object> buildMap(Integer userId,Integer productType,Date date,String conditionJson) {
Map<String, Object> map = Maps.newHashMap();
map.put("date", date);
map.put("userId", userId);
map.put("productType", productType);
if (StringUtils.isNotBlank(conditionJson)) {
Map<String, Object> conditionMap = JSON.parseObject(conditionJson, Map.class);
map.putAll(conditionMap);
}
return map;
}
五、执行Groovy 脚本
public Boolean proceed(String templateCode, Map<String, Object> map) {
GroovyObject groovyObject = groovyRepository.getByCode(templateCode);
Invocation invocation = new Invocation(groovyObject, "match", new Object[]{map});
Object result = invocation.proceed();
return (Boolean) result;
}