目录
前言
一、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 文件协议,用于规范 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 可以存在后端,随意分发动态渲染。
演示地址: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": "", // 标题,开发者维护时自用。在schema2code生成前端表单代码时,默认用于表单项前面的label
"description": "", // 描述,开发者维护时自用。在生成前端表单代码时,如果字段未设置componentForEdit,且字段被渲染为input,那么input的placehold将默认为本描述
"defaultValue": "", //默认值
"forceDefaultValue": "", // 强制默认值,不可通过clientDB的代码修改,常用于存放用户id、时间、客户端ip等固定值。
"required": [], // 是否必填。支持填写必填的下级字段名称。required可以在表级的描述出现,约定该表有哪些字段必填。也可以在某个字段中出现,如果该字段是一个json对象,可以对这个json中的哪些字段必填进行描述。
"enum": [], // 字段值枚举范围,数组中至少要有一个元素,且数组内的每一个元素都是唯一的。enum最多只可以枚举500条
"enumType": "", // 字段值枚举类型,可选值tree。设为tree时,代表enum里的数据为树形结构。此时schema2code可生成多级级联选择组件
"fileMediaType": "", // 文件类型,bsonType="file" 时有效,可选值 all|image|video 默认值为all,表示所有文件,image表示图片类型文件,video表示视频类型文件
"fileExtName": "", //文件扩展名过滤,bsonType="file" 时有效,多个文件扩展名用 "," 分割,例如: jpg,png,
"maximum": 20, //如果bsonType为数字时,可接受的最大值
"exclusiveMaximum": , //bsonType为数字时, 是否排除 maximum
"minimum": , //如果bsonType为数字时,可接受的最小值
"exclusiveMinimum", //bsonType为数字时, 是否排除 minimum
"minLength", //bsonType = String | Array时,限制字符串或数组的最小长度
"maxLength", //bsonType = String | Array时,限制字符串或数组的最大长度
"trim": "both", // bsonType = String 时,去除空白字符。可选值none|both|start|end,默认值none trim优先级高于其它驗证,它会在先去掉空格之后,再进行驗证。
"format": //枚举类型,可选值 url | email。设置字段数据格式。目前只支持这二种格式。数据不符合此格式时无法录入。
"pattern": //正则表达式,比如设置手机号码正则,如果该字段值不符合手机号正则,则录入失败。 验证手机号正则:"pattern": "^\\+?[0-9-]{3,20}$"
"validateFunction": "", //扩展校验函数名,用于校验该字段值是否符合要求
/* 值类型为:String 时表示校验函数名
值类型为object格式:
{
"name": """ //校验函数名
"client": true //默认值true。值为true时客户端也可以在生成的代码中改为自己的校验函数,此时客户端的校验仍然生效(不懂)
} */
"permission": { //
在项目开发中,我们经常需要定义一些规则,根据规则匹配处理相应的业务。比如在广告投放业务中,我们有以下规则的动态组合回传用户关键行为【注册,激活,支付等转化】。
如上,根据不同条件组合回传用户的关键行为非常适合用动态表单开发。首先我们先定义模版表
字段名称 | 字段类型 | 字段描述 |
---|---|---|
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 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 表单值] |
private Map buildMap(Integer userId,Integer productType,Date date,String conditionJson) {
Map map = Maps.newHashMap();
map.put("date", date);
map.put("userId", userId);
map.put("productType", productType);
if (StringUtils.isNotBlank(conditionJson)) {
Map conditionMap = JSON.parseObject(conditionJson, Map.class);
map.putAll(conditionMap);
}
return map;
}
public Boolean proceed(String templateCode, Map map) {
GroovyObject groovyObject = groovyRepository.getByCode(templateCode);
Invocation invocation = new Invocation(groovyObject, "match", new Object[]{map});
Object result = invocation.proceed();
return (Boolean) result;
}