Browse Source

feat(generator): 添加在线表单规则引擎及脚本事件支持

- 在OnlFormHeadServiceImpl中添加seq字段用于排序顺序控制
- 新增AbstractRuleActionExecutor基类提供Vo产出助手功能
- 实现BaseDataCarryExecutor支持携带基础资料功能
- 新增ConditionEvaluator实现规则条件求值逻辑
- 在EventEnum中添加SCRIPT_EVENT脚本事件类型
- 定义EventTypeEnum归一化事件类型枚举
- 扩展ExecutePluginParent添加scriptEvent方法
- 实现FieldAssignExecutor支持设置字段值功能
- 新增FieldClearExecutor实现清除字段值功能
- 创建FormRule模型定义规则结构
- 构建FormRuleEngine作为规则引擎核心入口
- 实现FormRuleParser解析规则配置
- 开发FormulaAssignExecutor处理计算赋值逻辑
feature/2026-07-ccc-dev
chenchuchuan 2 weeks ago
parent
commit
05530f7e7c
  1. 9
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
  2. 10
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ExecutePluginParent.java
  3. 311
      common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java
  4. 23
      common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java
  5. 8
      common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java
  6. 1
      common/common-online/src/main/java/apelet/common/online/model/constant/EventEnum.java
  7. 125
      common/common-online/src/main/java/apelet/common/online/rule/FormRuleEngine.java
  8. 39
      common/common-online/src/main/java/apelet/common/online/rule/RuleActionExecutorRegistry.java
  9. 105
      common/common-online/src/main/java/apelet/common/online/rule/action/AbstractRuleActionExecutor.java
  10. 21
      common/common-online/src/main/java/apelet/common/online/rule/action/RuleActionExecutor.java
  11. 105
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/BaseDataCarryExecutor.java
  12. 97
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldAssignExecutor.java
  13. 97
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldClearExecutor.java
  14. 162
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/FormulaAssignExecutor.java
  15. 27
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetHideExecutor.java
  16. 51
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetLockExecutor.java
  17. 27
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetShowExecutor.java
  18. 27
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetUnlockExecutor.java
  19. 121
      common/common-online/src/main/java/apelet/common/online/rule/condition/ConditionEvaluator.java
  20. 67
      common/common-online/src/main/java/apelet/common/online/rule/condition/FunctionExecutor.java
  21. 26
      common/common-online/src/main/java/apelet/common/online/rule/enums/EventTypeEnum.java
  22. 44
      common/common-online/src/main/java/apelet/common/online/rule/enums/RuleActionTypeEnum.java
  23. 23
      common/common-online/src/main/java/apelet/common/online/rule/enums/RuleTypeEnum.java
  24. 32
      common/common-online/src/main/java/apelet/common/online/rule/model/FormRule.java
  25. 45
      common/common-online/src/main/java/apelet/common/online/rule/model/RuleAction.java
  26. 31
      common/common-online/src/main/java/apelet/common/online/rule/model/RuleActionContext.java
  27. 29
      common/common-online/src/main/java/apelet/common/online/rule/model/RuleCondition.java
  28. 19
      common/common-online/src/main/java/apelet/common/online/rule/model/RuleEvalContext.java
  29. 110
      common/common-online/src/main/java/apelet/common/online/rule/parser/FormRuleParser.java
  30. 21
      common/common-online/src/main/java/apelet/common/online/service/IOnlineFormFieldSyncService.java
  31. 208
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormFieldSyncServiceImpl.java
  32. 252
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java
  33. 81
      common/common-online/src/main/java/apelet/common/online/util/Jexl3Util.java
  34. 51
      common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java

9
common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java

@ -781,6 +781,15 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
parentid.setIsEmpty(1); parentid.setIsEmpty(1);
parentid.setIsDefault(1); parentid.setIsDefault(1);
list.add(parentid); list.add(parentid);
OnlFormField seq = new OnlFormField();
seq.setFieldName("seq");
seq.setFieldRemark("排序顺序");
seq.setFieldType(CustomUtil.FieldType_int);
seq.setRowDisabled(1);
seq.setIsEmpty(1);
seq.setIsDefault(1);
list.add(seq);
} }
// //流程表单需要添加的参数 // //流程表单需要添加的参数

10
common/common-online/src/main/java/apelet/common/online/abstractplugin/ExecutePluginParent.java

@ -128,6 +128,16 @@ public abstract class ExecutePluginParent {
public void change(String widgetVariableName, ObjectValue objectValue) { public void change(String widgetVariableName, ObjectValue objectValue) {
} }
/**
* 脚本事件
*
* @param widgetVariableName 控件标识
* @param objectValue 表单数据
*/
public void scriptEvent(String widgetVariableName, ObjectValue objectValue) {
}
public void returnData(String widgetVariableName, ObjectValue objectValue) { public void returnData(String widgetVariableName, ObjectValue objectValue) {
} }

311
common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java

@ -1,311 +0,0 @@
package apelet.common.online.abstractplugin;
import apelet.common.core.constant.AppDeviceType;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.service.impl.OnlineFormServiceImpl;
import apelet.common.online.util.Jexl3Util;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class MyInterfaceRulesExecute extends ExecutePluginParent {
private OnlineFormServiceImpl onlineFormService;
private Jexl3Util jexl3Util;
public MyInterfaceRulesExecute() {
onlineFormService = ApplicationContextHolder.getBean("onlineFormService");
jexl3Util = ApplicationContextHolder.getBean("jexl3Util");
}
@Override
public void formCreated(String widgetVariableName, ObjectValue objectValue) {
Map values = objectValue.getValues();
List<Map<String, Object>> conditional = (List) values.get("conditional");
conditional("加载", widgetVariableName, conditional, objectValue);
List<Map<String, Object>> business = (List) values.get("business");
conditional("加载", widgetVariableName, business, objectValue);
}
@Override
public void change(String widgetVariableName, ObjectValue objectValue) {
Map values = objectValue.getValues();
List<Map<String, Object>> conditional = (List) values.get("conditional");
conditional("值更新", widgetVariableName, conditional, objectValue);
List<Map<String, Object>> business = (List) values.get("business");
conditional("值更新", widgetVariableName, business, objectValue);
}
void conditional(String type, String widgetVariableName, List<Map<String, Object>> isEstablish, ObjectValue objectValue) {
if (isEstablish == null || objectValue == null) {
return;
}
for (Map<String, Object> obj : isEstablish) {
List<JSONObject> allList = (List<JSONObject>) obj.get("allList");
List<JSONObject> condition;
if ((boolean) obj.get("establish")) {
condition = allList.stream().filter(f -> "1".equals(f.get("condition").toString())).collect(Collectors.toList());
} else {
condition = allList.stream().filter(f -> "2".equals(f.get("condition").toString())).collect(Collectors.toList());
}
//业务规则
d(type, widgetVariableName, condition, objectValue);
// 条件规则
b(type, widgetVariableName, condition);
}
}
void d(String pluginType, String widgetVariableName, List<JSONObject> jsonArray, ObjectValue objectValue) {
for (JSONObject obj : jsonArray) {
String checkList = obj.getJSONArray("checkList").toString();
if (checkList.contains(pluginType)) {
String type = obj.get("type").toString();
JSONArray treeList = obj.getJSONArray("treeList");
switch (type) {
case "1": { //计算定义公式的值并填写到指定列
String expressionTransl = obj.getString("expressionTranslation");
List<String> split = Arrays.asList(expressionTransl.split("="));
String fun = "";
for (String s : split) {
s = s.trim();
if (s.contains("$")) {
fun = s;
}
}
if (StringUtils.isEmpty(fun)) {
String tag = split.get(0);
String src = split.get(1);
if (src.contains(objectValue.getTableName())) {
// 公式引用表单字段(如 amount = price * qty):把 表.表.字段 引用替换为实际值后求值,再赋给目标字段
Map<String, Object> context = new HashMap<>();
for (String ref : src.split("[^a-zA-Z0-9_.]+")) {
if (!ref.contains(objectValue.getTableName()) || context.containsKey(ref)) {
continue;
}
context.put(ref, this.resolveFormulaField(ref, objectValue));
}
Object computed = jexl3Util.expression(src, context);
List<String> list = Arrays.asList(tag.split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, computed);
} else {
List<String> list = Arrays.asList(tag.split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.DATA_VALUE_CHANGE, src);
}
} else {
fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun);
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(","));
Object[] objects = jexl3Util.getFunParam(funParam, objectValue);
Object o = onlineFormService.executeFunction(obj, fun, objects);
List<String> list = Arrays.asList(split.get(0).split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, o);
}
break;
}
case "2": { //携带基础资料属性到指定列
JSONArray array = obj.getJSONArray("specifyJiList");
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj");
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String[] sourceFields = o2.get("sourceFields").toString().split("\\.");
String[] targetFields = o2.get("targetFields").toString().split("\\.");
if (sourceFields[0].equals(sourceFields[1])) {
ObjectValue o1 = (ObjectValue) objectValue.get(sourceFields[2]);
Object o11 = o1.get("id");
if (o11 != null) {
ObjectValue next1 = jexl3Util.getObjectValue(o1.get("id"), o1.getTableName());
Object values = next1.get(sourceFields[3]);
super.setWidgetAttribute(StrUtil.toCamelCase(targetFields[1]), AttributeEnum.VALUE_CHANGE, values);
}
} else {
ObjectCollection o3 = (ObjectCollection) objectValue.get(sourceFields[1]);
Iterator iterator = o3.iterator();
String rowKeySubset = getDto().getRowKeySubset();
Set<String> targetSet = new HashSet<String>();
if (rowKeySubset != null && !rowKeySubset.equals("")) {
String[] rowKeySubsetAry = rowKeySubset.split(",");
Collections.addAll(targetSet, rowKeySubsetAry);
}
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
String deviceType = AppDeviceType.getDeviceType();
if (
(StringUtils.isEmpty(rowKeySubset) && next.get("rowkey") != null) || //分录弹出界面新增时,rowkey 为空,所以为空时,为当前编辑数据
(next.get("rowkey") != null && !targetSet.contains(next.get("rowkey"))) //rowKey相等时,为当前编辑数据
) {
continue;
}
ObjectValue o4 = (ObjectValue) next.get(sourceFields[2]);
if (o4 == null) {
return;
}
o4 = jexl3Util.getObjectValue(o4.get("id"), o4.getTableName());
Object values = o4.get(sourceFields[3]);
Map rowChangeMap = (Map) objectValue.get("rowChange");
if (rowChangeMap == null) {//为空是弹出界面编辑,不为空行内编辑
super.setWidgetAttribute(StrUtil.toCamelCase(targetFields[1]), AttributeEnum.VALUE_CHANGE, values);
} else {
super.setWidgetAttributeByEntry(widgetVariableName, targetFields[1], AttributeEnum.VALUE_CHANGE, values,
next.getString("rowkey"));
}
}
}
}
break;
}
case "3": { //设置当前编辑字段值到指定字段
JSONArray array = obj.getJSONArray("specifyList");
Object v = "";
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String sourceFields = (String) o2.get("sourceFields");
List<String> list = Arrays.asList(sourceFields.split("\\."));
String filed = list.get(list.size() - 1);
String table = list.get(list.size() - 2);
if (table.equals(objectValue.getTableName())) {
v = objectValue.get(filed);
} else {
ObjectCollection o1 = (ObjectCollection) objectValue.get(table);
Iterator iterator = o1.iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
v = next.get(String.valueOf(filed));
}
}
String targetFields = (String) o2.get("targetFields");
List<String> list1 = Arrays.asList(targetFields.split("\\."));
super.setWidgetAttribute(list1.get(list1.size() - 1), AttributeEnum.VALUE_CHANGE, v);
}
break;
}
case "4": { //清除指定字段值
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
if (jsonObject.containsKey("zxName")) {
String[] split = jsonObject.getString("zxName").split("\\.");
if (split[0].equals(split[1])) {
String name = jsonObject.get("label").toString();
super.setWidgetAttribute(name, AttributeEnum.VALUE_CHANGE, "");
} else {
String tab = jexl3Util.getMySplit("\\((.*?)\\)", split[1]);
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj");
String tabId = (String) keyNameObj.getJSONArray(tab).get(0);
ObjectCollection collection = (ObjectCollection) objectValue.get(tab);
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
super.setWidgetAttributeByEntry(tabId, jsonObject.get("label").toString(), AttributeEnum.VALUE_CHANGE, "", next.getString("rowkey"));
}
}
}
}
break;
}
}
}
}
}
void b(String pluginType, String widgetVariableName, List<JSONObject> jsonArray) {
for (JSONObject obj : jsonArray) {
String checkList = obj.getJSONArray("checkList").toString();
if (checkList.contains(pluginType)) {
String type = obj.get("type").toString();
JSONArray treeList = obj.getJSONArray("treeList");
JSONArray objects = new JSONArray();
switch (type) {
case "5": { //锁定控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
if (jsonObject.containsKey("btnId")) {
String showName = jsonObject.get("showName").toString();
String parentVariableName = jsonObject.get("parentVariableName").toString();
JSONObject json = new JSONObject();
json.put("name", showName);
json.put("disable", true);
objects.add(json);
super.setWidgetAttribute(parentVariableName, AttributeEnum.CHANGEOPERATE, objects);
} else {
super.setWidgetAttribute(name, AttributeEnum.DISABLED, true);
}
}
break;
}
case "6": { //解锁控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
super.setWidgetAttribute(name, AttributeEnum.DISABLED, false);
}
break;
}
case "7": { //显示控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
super.setWidgetAttribute(name, AttributeEnum.SHOW, true);
}
break;
}
case "8": { //隐藏控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
super.setWidgetAttribute(name, AttributeEnum.SHOW, false);
}
break;
}
}
}
}
}
@Override
public void beforeSelector(String widgetVariableName, ObjectValue objectValue) {
super.beforeSelector(widgetVariableName, objectValue);
}
@Override
public void entryEditComplete(String widgetVariableName, ObjectValue objectValue) {
super.entryEditComplete(widgetVariableName, objectValue);
}
public String removeSymbols(String input) {
// 使用正则表达式替换所有非字母数字字符为空字符串
return input.replaceAll("_", "").toLowerCase();
}
/**
* 解析公式中 ..字段 引用的实际值
* 主表字段直接取值关联字段..关联列.子字段取子对象属性分录字段取第一行值空值统一补 0
*/
private Object resolveFormulaField(String ref, ObjectValue objectValue) {
String[] parts = ref.split("\\.");
Object value;
if (parts[0].equals(parts[1])) {
if (parts.length > 3) {
Object linked = objectValue.get(parts[2]);
value = linked instanceof ObjectValue ? ((ObjectValue) linked).get(parts[3]) : null;
} else {
value = objectValue.get(parts[2]);
}
} else {
ObjectCollection collection = (ObjectCollection) objectValue.get(parts[1]);
value = (collection == null || collection.isEmpty())
? null : ((ObjectValue) collection.iterator().next()).get(parts[2]);
}
return value == null || value.toString().isEmpty() ? 0 : value;
}
}

23
common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java

@ -97,6 +97,8 @@ public class OnlineFormController {
@Autowired @Autowired
OnlinePageService onlinePageService; OnlinePageService onlinePageService;
@Autowired @Autowired
private IOnlineFormFieldSyncService onlineFormFieldSyncService;
@Autowired
private OnlineProperties properties; private OnlineProperties properties;
@Resource(name = "caffeineCacheManager") @Resource(name = "caffeineCacheManager")
private CacheManager cacheManager; private CacheManager cacheManager;
@ -108,6 +110,25 @@ public class OnlineFormController {
private FormButtonPermissionChecker buttonPermissionChecker; private FormButtonPermissionChecker buttonPermissionChecker;
/** /**
* 自动同步字段到历史数据给所有缺该字段的 onl_form_head 表补字段可按表类型过滤
* 已同步的表补字段+同步数据库+回写 zz_online_column未同步的表只补字段
*
* @param fieldName 字段名
* @param type 字段类型数据库类型名如 varchar/bigint/decimal onl_fieldType 字典码 0~5
* @param length 字段长度varchar 默认 255decimal 为精度小数位默认 2
* @param description 字段描述
* @param tableType 表类型0 附表 / 1 主表 / 2 单表不传则全部
*/
@GetMapping(value = "/syncFieldToHistory")
public ResponseResult<?> syncFieldToHistory(@RequestParam String fieldName,
@RequestParam String type,
@RequestParam(required = false) Integer length,
@RequestParam(required = false) String description,
@RequestParam(required = false) String tableType) {
return ResponseResult.success(onlineFormFieldSyncService.syncFieldToHistory(fieldName, type, length, description, tableType));
}
/**
* 新增在线表单数据 * 新增在线表单数据
* *
* @param onlineFormDto 新增对象 * @param onlineFormDto 新增对象
@ -718,6 +739,8 @@ public class OnlineFormController {
return false; return false;
} }
for (String subTableName : newSubTableNames) { for (String subTableName : newSubTableNames) {
// 因为是附表,所以要加上
subTableName = "t_" + subTableName + "_entry";
OnlFormHead subHead = onlFormHeadService.createMetaAndSync(subTableName, onlFormHead.getTableTxt() + "附表", CustomUtil.MetaType_Entry, onlFormHead.getTableName()); OnlFormHead subHead = onlFormHeadService.createMetaAndSync(subTableName, onlFormHead.getTableTxt() + "附表", CustomUtil.MetaType_Entry, onlFormHead.getTableName());
// 获取子表的 OnlineTable // 获取子表的 OnlineTable
OnlineTable subOnlineTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, subTableName.toLowerCase())); OnlineTable subOnlineTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, subTableName.toLowerCase()));

8
common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java

@ -971,7 +971,7 @@ public class OnlineOperationController {
HashMap<String, Object> rowData = new HashMap<>(); HashMap<String, Object> rowData = new HashMap<>();
HashMap<String, Object> listData = new HashMap<>(); HashMap<String, Object> listData = new HashMap<>();
ObjectCollection collection = ormGenDataSourceUtil.query(masterTable.getTableName(), filter, selector); ObjectCollection collection = ormGenDataSourceUtil.query(masterTable.getTableName(), filter, selector);
if(collection.size() == 0){ if (collection.isEmpty()) {
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
rowData.put("id", id); rowData.put("id", id);
jsonObject.put("rowData", rowData); jsonObject.put("rowData", rowData);
@ -991,6 +991,12 @@ public class OnlineOperationController {
ObjectCollection entryCollection = (ObjectCollection) map.get(k); ObjectCollection entryCollection = (ObjectCollection) map.get(k);
List<Map<String, Object>> entryList = new ArrayList<>(); List<Map<String, Object>> entryList = new ArrayList<>();
onlineOperationService.collectionToJson(entryCollection, entryList); onlineOperationService.collectionToJson(entryCollection, entryList);
String sort = "seq";
entryList = entryList.stream().sorted(
Comparator.comparing(m -> m.containsKey(sort) && m.get(sort) != null && StringUtils.isNotBlank(m.get(sort).toString())
? Double.valueOf(m.get(sort).toString()) : null,
Comparator.nullsLast(Double::compareTo)))
.collect(Collectors.toList());
listData.put(String.valueOf(entryIdMap.get(k)), entryList); listData.put(String.valueOf(entryIdMap.get(k)), entryList);
} else { } else {
rowData.put(k, map.get(k)); rowData.put(k, map.get(k));

1
common/common-online/src/main/java/apelet/common/online/model/constant/EventEnum.java

@ -42,6 +42,7 @@ public enum EventEnum {
MENU_CLICK("menuClick", "菜单点击事件"), MENU_CLICK("menuClick", "菜单点击事件"),
OPERATION_AFTER("operationAfter", "保存后"), OPERATION_AFTER("operationAfter", "保存后"),
TIMER_ELAPSED("timerElapsed", "前端定时发送请求,触发此界面事件。"), TIMER_ELAPSED("timerElapsed", "前端定时发送请求,触发此界面事件。"),
SCRIPT_EVENT("scriptEvent", "脚本事件"),
; ;

125
common/common-online/src/main/java/apelet/common/online/rule/FormRuleEngine.java

@ -0,0 +1,125 @@
package apelet.common.online.rule;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.rule.action.RuleActionExecutor;
import apelet.common.online.rule.condition.ConditionEvaluator;
import apelet.common.online.rule.enums.RuleTypeEnum;
import apelet.common.online.rule.model.FormRule;
import apelet.common.online.rule.model.RuleAction;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.rule.model.RuleEvalContext;
import apelet.common.online.rule.parser.FormRuleParser;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* 规则引擎入口解析 规则级一次求值 condition 选动作 事件匹配 路由执行器
*/
@Component
public class FormRuleEngine {
@Resource
private FormRuleParser parser;
@Resource
private ConditionEvaluator conditionEvaluator;
@Resource
private RuleActionExecutorRegistry registry;
public List<Object> execute(OnlineForm form, ObjectValue ov, String eventCode, OnlineEventPluginExecuteDto dto) {
List<Object> result = new ArrayList<>();
if (form == null || form.getWidgetJson() == null) {
return result;
}
for (FormRule rule : parser.parse(form.getWidgetJson())) {
executeRule(rule, form, ov, eventCode, dto, result);
}
return result;
}
/**
* 通用动作执行入口给定单条规则的 JSON 字段值规则来源类型
* 解析 条件求值 condition 选动作 路由执行器 产出 Vo 全流程
* 通用入口不按事件过滤eventCode nullcheckList 只作展示不拦截执行
*
* @param ruleJson 单条规则 JSON businessRulesList / conditionTypeList 数组元素expressionTranslationallListEnable
* @param values 字段取值key 为字段名或完整 ..字段 / .关联对象.字段
* @param ruleType 规则来源类型界面规则 / 业务规则
*/
public List<Object> executeAction(String ruleJson, Map<String, Object> values, RuleTypeEnum ruleType) {
if (StringUtils.isBlank(ruleJson)) {
return null;
}
FormRule rule = parser.parseSingle(JSONObject.parseObject(ruleJson), ruleType);
if (rule == null) {
return null;
}
List<Object> result = new ArrayList<>();
executeRule(rule, null, buildObjectValue(rule.getCondition().getExpression(), values), null,
new OnlineEventPluginExecuteDto(), result);
return result;
}
private void executeRule(FormRule rule, OnlineForm form, ObjectValue ov, String eventCode,
OnlineEventPluginExecuteDto dto, List<Object> result) {
if (!rule.isEnable()) {
return;
}
// 规则级:表达式只求值一次
boolean establish = conditionEvaluator.evaluate(rule.getCondition(), new RuleEvalContext(ov, dto));
// 规则内动作按 sort 升序执行(稳定排序,缺失/相同的保持数组顺序)
rule.getActions().sort(Comparator.comparingInt(RuleAction::getSort));
for (RuleAction action : rule.getActions()) {
// 成立 → condition=1;不成立 → condition=2
if ((establish && action.getCondition() != 1) || (!establish && action.getCondition() != 2)) {
continue;
}
// executeAction 通用入口 eventCode 为 null:不按事件过滤,全部执行
if (eventCode != null && !action.matchEvent(eventCode)) {
continue;
}
RuleActionExecutor executor = registry.get(action.getActionType());
RuleActionContext ctx = new RuleActionContext(form, ov, dto, action, eventCode,
dto == null ? null : dto.getEventWidgetVariableName(), rule.getCondition().getFunctionForm());
Object value = executor.resolveValue(ctx);
result.addAll(executor.buildResult(ctx, value));
}
}
private ObjectValue buildObjectValue(String expression, Map<String, Object> values) {
ObjectValue objectValue = new ObjectValue(extractTableName(expression));
if (values != null) {
for (Map.Entry<String, Object> entry : values.entrySet()) {
objectValue.put(lastSegment(entry.getKey()), entry.getValue());
}
}
return objectValue;
}
/**
* 从表达式里取主表名 "t_flowtest001.t_flowtest001.amount = ..." "t_flowtest001"
*/
private String extractTableName(String expressionTranslation) {
if (StringUtils.isBlank(expressionTranslation)) return "";
int eq = expressionTranslation.indexOf('=');
String tag = (eq < 0 ? expressionTranslation : expressionTranslation.substring(0, eq)).trim();
int dot = tag.indexOf('.');
return dot < 0 ? tag : tag.substring(0, dot);
}
/**
* key 末段完整 ..字段 或字段名 字段名
*/
private String lastSegment(String key) {
int dot = key.lastIndexOf('.');
return dot < 0 ? key : key.substring(dot + 1);
}
}

39
common/common-online/src/main/java/apelet/common/online/rule/RuleActionExecutorRegistry.java

@ -0,0 +1,39 @@
package apelet.common.online.rule;
import apelet.common.online.rule.action.RuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 动作类型 执行器 路由选择器
* 常量 map 在构造器注入时一次性初始化运行期只读无需反复构建
* 加新动作类型 = 加一个执行器Spring 注入后自动注册无需改这里
*/
@Slf4j
@Component
public class RuleActionExecutorRegistry {
/** 动作类型 → 执行器 常量映射,构造器初始化,运行期只读 */
private final Map<RuleActionTypeEnum, RuleActionExecutor> executors = new HashMap<>();
public RuleActionExecutorRegistry(List<RuleActionExecutor> executorList) {
for (RuleActionExecutor executor : executorList) {
executors.put(executor.getActionType(), executor);
}
log.info("规则执行器初始化完成,共注册 {} 个动作类型:{}", executors.size(), executors.keySet());
}
/** 按动作类型路由执行器 */
public RuleActionExecutor get(RuleActionTypeEnum actionType) {
RuleActionExecutor executor = executors.get(actionType);
if (executor == null) {
throw new IllegalArgumentException("未注册的动作类型: " + actionType);
}
return executor;
}
}

105
common/common-online/src/main/java/apelet/common/online/rule/action/AbstractRuleActionExecutor.java

@ -0,0 +1,105 @@
package apelet.common.online.rule.action;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.WidgetJsonUtil;
import apelet.common.online.vo.OnlineEventPluginExecuteEntryVo;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 执行器基类自带 Vo 产出助手不复用 ExecutePluginParent插件机制独立
*/
public abstract class AbstractRuleActionExecutor implements RuleActionExecutor {
/** 默认取值:整表单(type5~8 无重活,直接用默认) */
@Override
public Object resolveValue(RuleActionContext ctx) {
return ctx.getObjectValue();
}
protected OnlineEventPluginExecuteVo buildVo(String widgetVariableName, AttributeEnum type, Object value) {
OnlineEventPluginExecuteVo vo = new OnlineEventPluginExecuteVo();
vo.setWidgetVariableName(widgetVariableName);
vo.setWidgetOperateType(type.getCode());
vo.setWidgetOperateContent(value instanceof ObjectValue ? ((ObjectValue) value).getValues() : value);
return vo;
}
protected OnlineEventPluginExecuteEntryVo buildEntryVo(String name, String col, AttributeEnum type, Object value, String rowNum) {
OnlineEventPluginExecuteEntryVo vo = new OnlineEventPluginExecuteEntryVo();
vo.setWidgetVariableName(name);
vo.setEntriesTableColumnName(col);
vo.setWidgetOperateType(type.getCode());
vo.setWidgetOperateContent(value);
vo.setRowNum(rowNum);
return vo;
}
/**
* 列名 控件 variableName 列表从表单 widgetJson pc/mobile.widgetList bindData.columnFieldName 反查
* 未配置映射的列名原样返回自身一个列名对应多个控件多个控件绑定同一列时返回多个 variableName
*/
protected List<String> resolveWidgetVariableNames(RuleActionContext ctx, String column) {
OnlineForm form = ctx.getForm();
if (form != null && StringUtils.isNotBlank(form.getWidgetJson())) {
Map<String, List<String>> map = WidgetJsonUtil.collectColumnVariableNames(JSONObject.parseObject(form.getWidgetJson()));
List<String> names = map.get(column);
if (names != null && !names.isEmpty()) {
return names;
}
}
return Collections.singletonList(column);
}
/** treeList 控件目标 → 一组普通指令(type 6/7/8 用) */
protected List<OnlineEventPluginExecuteVo> buildFromTreeList(RuleActionContext ctx, AttributeEnum type, Object attrValue) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList");
if (treeList == null) return out;
for (Object o : treeList) {
JSONObject json = (JSONObject) o;
out.add(buildVo(json.get("variableName").toString(), type, attrValue));
}
return out;
}
/** 分录 tabId:keyNameObj 反查(主表/已登记分录),缺失时兜底用事件触发控件名(分录内触发即分录表控件),均无则返回 null 不产出分录 Vo */
protected String resolveEntryTabId(ObjectValue objectValue, String tableName, String eventWidgetVariableName) {
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj");
if (keyNameObj != null) {
JSONArray tabIds = keyNameObj.getJSONArray(tableName);
if (tabIds != null && !tabIds.isEmpty()) {
return tabIds.get(0).toString();
}
}
return eventWidgetVariableName;
}
/**
* 从目标/源字段路径识别附表名目标 主表.分录表.字段 / 分录表.字段 优先 主表.分录表.字段 兜底附表内字段影响附表字段时目标随源
* 主表目标.字段 / ..字段返回 null
*/
protected String resolveEntryTable(ObjectValue objectValue, String[] targetParts, String[] sourceParts) {
if (targetParts.length >= 3 && !targetParts[0].equals(targetParts[1]) && objectValue.get(targetParts[1]) instanceof ObjectCollection) {
return targetParts[1];
}
if (targetParts.length == 2 && objectValue.get(targetParts[0]) instanceof ObjectCollection) {
return targetParts[0];
}
if (sourceParts.length >= 3 && !sourceParts[0].equals(sourceParts[1]) && objectValue.get(sourceParts[1]) instanceof ObjectCollection) {
return sourceParts[1];
}
return null;
}
}

21
common/common-online/src/main/java/apelet/common/online/rule/action/RuleActionExecutor.java

@ -0,0 +1,21 @@
package apelet.common.online.rule.action;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import java.util.List;
/**
* 规则动作执行器一个动作类型一个执行器三方法契约
*/
public interface RuleActionExecutor {
/** ① 路由标识:本执行器负责的动作类型,Registry 靠它路由 */
RuleActionTypeEnum getActionType();
/** ② 取值:读取执行所需的字段/数据(各动作类型差异最大) */
Object resolveValue(RuleActionContext ctx);
/** ③ 返回结果:产出前端指令 */
List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value);
}

105
common/common-online/src/main/java/apelet/common/online/rule/action/impl/BaseDataCarryExecutor.java

@ -0,0 +1,105 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
* type 2 携带基础资料主表直接带出分录按 rowKeySubset 只带当前编辑行行内编辑带 rowNum
*/
@Component
public class BaseDataCarryExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.BASE_DATA_CARRY;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
List<CarryValue> values = new ArrayList<>();
ObjectValue objectValue = ctx.getObjectValue();
JSONArray array = ctx.getAction().getSource().getJSONArray("specifyJiList");
if (array == null) {
return values;
}
String rowKeySubset = ctx.getDto().getRowKeySubset();
String rowKey = ctx.getDto().getRowKey();
Set<String> targetSet = new HashSet<>();
if (StringUtils.isNotEmpty(rowKeySubset)) {
Collections.addAll(targetSet, rowKeySubset.split(","));
}
Map rowChangeMap = (Map) objectValue.get("rowChange");
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String[] parts = o2.get("sourceFields").toString().split("\\.");
String[] targetParts = o2.get("targetFields").toString().split("\\.");
String targetField = targetParts[targetParts.length - 1];
Object first = objectValue.get(parts[1]);
if (!parts[0].equals(parts[1]) && first instanceof ObjectCollection) { // 分录:主表.分录表.关联对象.字段
Iterator iterator = ((ObjectCollection) first).iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
String k = next.getString("rowkey");
if (StringUtils.isNotEmpty(rowKeySubset)) { // 行内编辑:命中 subset 的行
if (k == null || !targetSet.contains(k)) continue;
} else if (StringUtils.isNotEmpty(rowKey)) { // 分录编辑完成:命中当前行 rowKey
if (!rowKey.equals(k)) continue;
} else { // 无过滤:跳过已保存行,只处理新增行
if (k != null) continue;
}
String rowNum = rowChangeMap == null ? null : next.getString("rowkey"); // 行内编辑才带 rowNum
Object value = jexl3Util.resolveFieldChain(next, parts, 2);
next.put(targetField, value); // 写回分录行,供后续规则条件求值
values.add(new CarryValue(targetField, value, rowNum));
}
} else { // 主表:表.关联对象.子字段 / 表.表.字段
Object value = jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1);
objectValue.put(targetField, value); // 写回主表,供后续规则条件求值
values.add(new CarryValue(targetField, value, null));
}
}
return values;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
for (CarryValue v : (List<CarryValue>) value) {
if (v.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, v.targetField)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, v.value));
}
} else {
out.add(buildEntryVo(ctx.getEventWidgetVariableName(), v.targetField, AttributeEnum.VALUE_CHANGE, v.value, v.rowNum));
}
}
return out;
}
private static class CarryValue {
final String targetField;
final Object value;
final String rowNum;
CarryValue(String targetField, Object value, String rowNum) {
this.targetField = targetField;
this.value = value;
this.rowNum = rowNum;
}
}
}

97
common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldAssignExecutor.java

@ -0,0 +1,97 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* type 3 设置字段值sourceFields 灵活解析主表 .字段 / .关联对象.子字段分录取当前编辑行
* 目标字段归属分录表时写当前编辑行并产分录 Vo主表目标写主表对象
*/
@Component
public class FieldAssignExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.FIELD_ASSIGN;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
List<AssignValue> values = new ArrayList<>();
ObjectValue objectValue = ctx.getObjectValue();
JSONArray array = ctx.getAction().getSource().getJSONArray("specifyList");
if (array == null) {
return values;
}
String rowKeySubset = ctx.getDto().getRowKeySubset();
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String sourceFields = (String) o2.get("sourceFields");
String targetFields = (String) o2.get("targetFields");
String[] sourceParts = sourceFields.split("\\.");
String[] targetParts = targetFields.split("\\.");
String targetField = targetParts[targetParts.length - 1];
Object v = jexl3Util.resolveRefValue(sourceFields, objectValue, rowKeySubset);
String entryTable = resolveEntryTable(objectValue, targetParts, sourceParts);
ObjectValue row = entryTable == null ? null : jexl3Util.currentRow((ObjectCollection) objectValue.get(entryTable), rowKeySubset, ctx.getDto().getRowKey());
if (entryTable != null && row == null) {
continue; // 附表目标但表为空,跳过,避免误写主表
}
if (row != null) {
row.put(targetField, v); // 附表目标:写回当前编辑行
String tabId = resolveEntryTabId(objectValue, entryTable, ctx.getEventWidgetVariableName());
if (tabId != null) {
values.add(new AssignValue(targetField, v, tabId, row.getString("rowkey")));
}
} else {
objectValue.put(targetField, v); // 主表目标:写回表单对象,供后续规则条件求值
values.add(new AssignValue(targetField, v, null, null));
}
}
return values;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
for (AssignValue v : (List<AssignValue>) value) {
if (v.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, v.targetField)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, v.value));
}
} else {
out.add(buildEntryVo(v.tabId, v.targetField, AttributeEnum.VALUE_CHANGE, v.value, v.rowNum));
}
}
return out;
}
private static class AssignValue {
final String targetField;
final Object value;
final String tabId;
final String rowNum;
AssignValue(String targetField, Object value, String tabId, String rowNum) {
this.targetField = targetField;
this.value = value;
this.tabId = tabId;
this.rowNum = rowNum;
}
}
}

97
common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldClearExecutor.java

@ -0,0 +1,97 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* type 4 清除字段值主表直接清分录从主表对象取集合后逐行清空表跳过tabId keyNameObj 反查缺失兜底事件控件
*/
@Component
public class FieldClearExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.FIELD_CLEAR;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
List<ClearValue> values = new ArrayList<>();
ObjectValue objectValue = ctx.getObjectValue();
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList");
if (treeList == null) {
return values;
}
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
if (jsonObject.containsKey("zxName")) {
String[] split = jsonObject.getString("zxName").split("\\.");
if (split[0].equals(split[1])) { // 主表字段
String label = jsonObject.get("label").toString();
objectValue.put(label, ""); // 写回清空,供后续规则条件求值
values.add(new ClearValue(label, null, null));
} else { // 分录字段:主表对象里取分录集合,清当前编辑行
String tab = jexl3Util.getMySplit("\\((.*?)\\)", split[1]);
ObjectCollection collection = (ObjectCollection) objectValue.get(tab); // 从主表对象取分录集合(新增空分录表时为空)
if (collection == null || collection.isEmpty()) {
continue; // 附表为空,无行可清
}
ObjectValue current = jexl3Util.currentRow(collection, ctx.getDto().getRowKeySubset(), ctx.getDto().getRowKey());
if (current == null) {
continue; // 无当前编辑行(表单加载等),跳过不清,避免误清其他行
}
String label = jsonObject.get("label").toString();
String tabId = resolveEntryTabId(objectValue, tab, ctx.getEventWidgetVariableName());
current.put(label, ""); // 只清当前编辑行,避免连带清空其他行
if (tabId != null) {
values.add(new ClearValue(label, tabId, current.getString("rowkey")));
}
}
}
}
return values;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
for (ClearValue v : (List<ClearValue>) value) {
if (v.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, v.field)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, ""));
}
} else {
out.add(buildEntryVo(v.tabId, v.field, AttributeEnum.VALUE_CHANGE, "", v.rowNum));
}
}
return out;
}
private static class ClearValue {
final String field;
final String tabId;
final String rowNum;
ClearValue(String field, String tabId, String rowNum) {
this.field = field;
this.tabId = tabId;
this.rowNum = rowNum;
}
}
}

162
common/common-online/src/main/java/apelet/common/online/rule/action/impl/FormulaAssignExecutor.java

@ -0,0 +1,162 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.condition.FunctionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
/**
* type 1 计算并赋值 expressionTranslation公式引用字段算值字面量直接赋$fun$函数
* 目标字段归属分录表时写当前编辑行并产分录 Vo主表目标写主表对象
*/
@Component
public class FormulaAssignExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Resource
private FunctionExecutor functionExecutor;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.FORMULA_ASSIGN;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
String expressionTranslation = ctx.getAction().getSource().getString("expressionTranslation");
if (expressionTranslation == null || !expressionTranslation.contains("=")) {
return null;
}
String[] split = expressionTranslation.split("=");
String tag = split[0].trim();
String src = split[1].trim();
ObjectValue objectValue = ctx.getObjectValue();
String targetField = lastSegment(tag);
String rowKeySubset = ctx.getDto().getRowKeySubset();
Object result;
if (src.contains("$")) { // 分支 C:$fun$ 函数赋值
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", src);
result = functionExecutor.execute(ctx.getFunctionForm(), fun, objectValue);
} else if (!src.contains(objectValue.getTableName())) { // 分支 B:字面量直接赋值
result = parseLiteral(src);
} else { // 分支 A:公式引用字段,替换为实际值后求值(空值补 0)
Map<String, Object> context = new HashMap<>();
for (String ref : extractFieldRefs(src)) {
if (ref.contains(objectValue.getTableName()) && !context.containsKey(ref)) {
context.put(ref, resolveFormulaField(ref, objectValue, rowKeySubset));
}
}
result = jexl3Util.expression(src, context);
}
String[] tagParts = tag.split("\\.");
List<String> srcRefs = extractFieldRefs(src);
String[] srcParts = srcRefs.isEmpty() ? new String[0] : srcRefs.get(0).split("\\.");
String entryTable = resolveEntryTable(objectValue, tagParts, srcParts);
ObjectValue row = entryTable == null ? null : jexl3Util.currentRow((ObjectCollection) objectValue.get(entryTable), rowKeySubset, ctx.getDto().getRowKey());
if (entryTable != null && row == null) {
return null; // 附表目标但表为空,跳过,避免误写主表
}
if (row != null) {
row.put(targetField, result); // 附表目标:写回当前编辑行
String tabId = resolveEntryTabId(objectValue, entryTable, ctx.getEventWidgetVariableName());
return tabId == null ? null : new FormulaValue(targetField, result, tabId, row.getString("rowkey"));
}
objectValue.put(targetField, result); // 主表目标:写回表单对象,供后续规则条件求值
return new FormulaValue(targetField, result, null, null);
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
FormulaValue fv = (FormulaValue) value;
if (fv == null) {
return Collections.emptyList();
}
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
if (fv.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, fv.targetField)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, fv.value));
}
} else {
out.add(buildEntryVo(fv.tabId, fv.targetField, AttributeEnum.VALUE_CHANGE, fv.value, fv.rowNum));
}
return out;
}
private String lastSegment(String tag) {
String[] parts = tag.split("\\.");
return parts[parts.length - 1].trim();
}
/** 解析公式中 表.表.字段 / 表.字段 引用的实际值:主表直接取/下钻关联对象,分录取当前编辑行;空值补 0,数字字符串转数值(避免字符串相加) */
private Object resolveFormulaField(String ref, ObjectValue objectValue, String rowKeySubset) {
String[] parts = ref.split("\\.");
Object value;
Object first = objectValue.get(parts[1]);
if (!parts[0].equals(parts[1]) && first instanceof ObjectCollection) { // 分录:表.分录表.字段
ObjectValue row = jexl3Util.currentRow((ObjectCollection) first, rowKeySubset, (String) objectValue.get("rowKey"));
value = row == null ? null : row.get(parts[2]);
} else { // 主表:表.字段 / 表.表.字段 / 表.关联对象.子字段
value = jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1);
}
if (value == null || value.toString().isEmpty()) {
return 0;
}
return value instanceof String ? toNumber((String) value) : value;
}
/** 解析字面量赋值:去首尾引号,'' → 空串,数字字符串转数值,其余原样返回 */
private Object parseLiteral(String src) {
String trim = src.trim();
if (trim.length() >= 2
&& ((trim.startsWith("'") && trim.endsWith("'")) || (trim.startsWith("\"") && trim.endsWith("\"")))) {
trim = trim.substring(1, trim.length() - 1);
}
return toNumber(trim);
}
/** 数字字符串转数值:正则判数字 → 整数转 Long,小数转 BigDecimal;非数字原样返回 */
private Object toNumber(String s) {
String trim = s.trim();
if (!trim.matches("-?\\d+(\\.\\d+)?")) {
return s;
}
if (trim.contains(".")) {
return new BigDecimal(trim);
}
return Long.valueOf(trim);
}
private List<String> extractFieldRefs(String src) {
List<String> refs = new ArrayList<>();
for (String token : src.split("[^a-zA-Z0-9_.]+")) {
if (!token.isEmpty() && token.split("\\.").length >= 2 && !refs.contains(token)) {
refs.add(token);
}
}
return refs;
}
private static class FormulaValue {
final String targetField;
final Object value;
final String tabId;
final String rowNum;
FormulaValue(String targetField, Object value, String tabId, String rowNum) {
this.targetField = targetField;
this.value = value;
this.tabId = tabId;
this.rowNum = rowNum;
}
}
}

27
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetHideExecutor.java

@ -0,0 +1,27 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* type 8 隐藏控件 SHOW=false
*/
@Component
public class WidgetHideExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_HIDE;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
return buildFromTreeList(ctx, AttributeEnum.SHOW, false);
}
}

51
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetLockExecutor.java

@ -0,0 +1,51 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* type 5 锁定控件普通控件 DISABLEDbtnId 特例锁父容器内按钮 CHANGEOPERATE 数组
*/
@Component
public class WidgetLockExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_LOCK;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList");
if (treeList == null) {
return out;
}
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
if (jsonObject.containsKey("btnId")) { // 特例:锁定父容器内按钮
String showName = jsonObject.get("showName").toString();
String parentVariableName = jsonObject.get("parentVariableName").toString();
JSONArray objects = new JSONArray();
JSONObject json = new JSONObject();
json.put("name", showName);
json.put("disable", true);
objects.add(json);
out.add(buildVo(parentVariableName, AttributeEnum.CHANGEOPERATE, objects));
} else { // 普通控件锁定
out.add(buildVo(name, AttributeEnum.DISABLED, true));
}
}
return out;
}
}

27
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetShowExecutor.java

@ -0,0 +1,27 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* type 7 显示控件 SHOW=true
*/
@Component
public class WidgetShowExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_SHOW;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
return buildFromTreeList(ctx, AttributeEnum.SHOW, true);
}
}

27
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetUnlockExecutor.java

@ -0,0 +1,27 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* type 6 解锁控件 DISABLED=false
*/
@Component
public class WidgetUnlockExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_UNLOCK;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
return buildFromTreeList(ctx, AttributeEnum.DISABLED, false);
}
}

121
common/common-online/src/main/java/apelet/common/online/rule/condition/ConditionEvaluator.java

@ -0,0 +1,121 @@
package apelet.common.online.rule.condition;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.rule.model.RuleCondition;
import apelet.common.online.rule.model.RuleEvalContext;
import apelet.common.online.util.Jexl3Util;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 规则级条件求值一条规则只求值一次用结果选 condition=1/2 的动作
* 加载表达式里所有..字段 / .字段 token context修复 getExpressionValue 只取第一个字段
*/
@Component
public class ConditionEvaluator {
private static final Pattern FIELD_REF = Pattern.compile("[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+");
@Resource
private Jexl3Util jexl3Util;
@Resource
private FunctionExecutor functionExecutor;
public boolean evaluate(RuleCondition condition, RuleEvalContext ctx) {
String expression = condition.getExpression();
if (StringUtils.isEmpty(expression)) return false;
boolean result = true;
for (String expr : expression.split("&&")) {
expr = expr.trim();
if (expr.isEmpty()) continue;
if (expr.startsWith("$")) {
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", expr);
Object value = functionExecutor.execute(condition.getFunctionForm(), fun, ctx.getObjectValue());
result = jexl3Util.judgmentFunc(expr, value) && result;
} else {
result = evaluateSimple(expr, ctx.getObjectValue()) && result;
}
}
return result;
}
private boolean evaluateSimple(String expr, ObjectValue objectValue) {
List<String> refs = extractFieldRefs(expr);
Map<String, Object> context = new HashMap<>();
String entryTable = null;
for (String ref : refs) {
String[] parts = ref.split("\\.");
if (parts.length < 2) continue;
// 分录:起始段在 objectValue 里是分录集合;其余都是主表字段(兼容 表.表.字段 / 表.字段 两种格式)
if (!parts[0].equals(parts[1]) && objectValue.get(parts[1]) instanceof ObjectCollection) {
if (entryTable == null) entryTable = parts[1];
} else {
context.put(ref, resolveMasterField(ref, objectValue));
}
}
if (entryTable == null) {
Object val = jexl3Util.expression(expr, context);
return val instanceof Boolean && (Boolean) val;
}
return evaluateEntryRows(expr, refs, entryTable, context, objectValue);
}
/** 分录表达式:命中当前编辑行逐行求值(rowKeySubset 行内编辑 / rowKey 分录编辑完成 / 无过滤取新增行),任一行为 true 即成立 */
private boolean evaluateEntryRows(String expr, List<String> refs, String entryTable,
Map<String, Object> baseContext, ObjectValue objectValue) {
ObjectCollection rows = (ObjectCollection) objectValue.get(entryTable);
if (rows == null || rows.isEmpty()) return false;
String rowKeySubset = (String) objectValue.get("rowKeySubset");
String rowKey = (String) objectValue.get("rowKey");
Set<String> targetSet = new HashSet<>();
if (StringUtils.isNotEmpty(rowKeySubset)) Collections.addAll(targetSet, rowKeySubset.split(","));
Iterator iterator = rows.iterator();
while (iterator.hasNext()) {
ObjectValue row = (ObjectValue) iterator.next();
if (row.getValues() == null || row.getValues().isEmpty()) continue;
String k = row.getString("rowkey");
if (StringUtils.isNotEmpty(rowKeySubset)) { // 行内编辑:命中 subset 的行
if (k == null || !targetSet.contains(k)) continue;
} else if (StringUtils.isNotEmpty(rowKey)) { // 分录编辑完成:命中当前行 rowKey
if (!rowKey.equals(k)) continue;
} else { // 无过滤:跳过已保存行,只评估新增行
if (k != null) continue;
}
Map<String, Object> context = new HashMap<>(baseContext);
for (String ref : refs) {
String[] parts = ref.split("\\.");
if (parts.length >= 2 && entryTable.equals(parts[1])) {
context.put(ref, resolveRowField(row, parts));
}
}
Object val = jexl3Util.expression(expr, context);
if (val instanceof Boolean && (Boolean) val) return true;
}
return false;
}
private Object resolveMasterField(String ref, ObjectValue objectValue) {
String[] parts = ref.split("\\.");
return jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1);
}
private Object resolveRowField(ObjectValue row, String[] parts) {
return jexl3Util.resolveFieldChain(row, parts, 2);
}
private List<String> extractFieldRefs(String expr) {
List<String> refs = new ArrayList<>();
Matcher matcher = FIELD_REF.matcher(expr);
while (matcher.find()) {
String ref = matcher.group();
if (!refs.contains(ref)) refs.add(ref);
}
return refs;
}
}

67
common/common-online/src/main/java/apelet/common/online/rule/condition/FunctionExecutor.java

@ -0,0 +1,67 @@
package apelet.common.online.rule.condition;
import apelet.common.core.exception.MyRuntimeException;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.SysFunction;
import apelet.common.online.service.ISysFunctionService;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.SysFunctionVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* 封装 $fun$ 动态编译函数调用javassist条件求值与 type1 动作赋值共用
*/
@Component
public class FunctionExecutor {
@Resource
private ISysFunctionService sysFunctionService;
@Resource
private Jexl3Util jexl3Util;
/** 规则引擎路径:提取函数参数后执行 */
public Object execute(Map<String, Long> functionForm, String fun, ObjectValue objectValue) {
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(","));
Object[] objects = jexl3Util.getFunParam(funParam, objectValue);
return executeWithParams(functionForm, fun, objects);
}
/** 核心调用:给定已提取的参数,查函数并动态编译执行(Save/Audit 插件经 OnlineFormServiceImpl.executeFunction 也走这里) */
public Object executeWithParams(Map<String, Long> functionForm, String fun, Object[] objects) {
if (fun == null) {
return null;
}
String funName = fun.substring(0, fun.indexOf('(')).trim();
SysFunction sysFunction = resolveSysFunction(functionForm, funName);
SysFunctionVo sysFunctionVo = new SysFunctionVo();
BeanUtils.copyProperties(sysFunction, sysFunctionVo);
sysFunctionVo.setParameter(objects);
return sysFunctionService.onLineFunction(sysFunctionVo);
}
/**
* 解析 $fun$ 引用的函数functionForm 映射的 ID 优先 getByIdID 缺失/查不到时按函数名或编码function_name/number查询
* 兼容 functionName 字段后续改为 number 字段名称或编码匹配到 0 个或多个视为配置错误直接失效
*/
private SysFunction resolveSysFunction(Map<String, Long> functionForm, String funName) {
Long funId = functionForm == null ? null : functionForm.get(funName);
if (funId != null) {
SysFunction f = sysFunctionService.getById(funId);
if (f != null) return f;
}
List<SysFunction> list = sysFunctionService.list(new LambdaQueryWrapper<SysFunction>()
.eq(SysFunction::getFunctionName, funName)
.or().eq(SysFunction::getNumber, funName));
if (list == null || list.size() != 1) {
throw new MyRuntimeException("函数 [" + funName + "] 不存在或匹配到多个,无法执行 $fun$");
}
return list.get(0);
}
}

26
common/common-online/src/main/java/apelet/common/online/rule/enums/EventTypeEnum.java

@ -0,0 +1,26 @@
package apelet.common.online.rule.enums;
import lombok.Getter;
/**
* 归一化后的事件类型由动作 checkList 归一化而来
* "加载" FORM_CREATED"值更新"/"值改变" CHANGE空数组 ALL监听全部事件
*
* 触发匹配eventCode = formCreated 命中 FORM_CREATED其余命中 CHANGEALL 恒命中
*/
public enum EventTypeEnum {
/** 全部事件:checkList 为空数组,动作在所有事件下都触发(修复空数组「永不执行」行为反转点) */
ALL("全部事件(checkList 为空)"),
/** 页面加载:checkList 含「加载」,对应 eventCode = formCreated */
FORM_CREATED("页面加载(checkList 含「加载」,formCreated)"),
/** 值改变:checkList 含「值更新」/「值改变」,对应 eventCode = change */
CHANGE("值改变(checkList 含「值更新」/「值改变」,change)");
@Getter
private final String description;
EventTypeEnum(String description) {
this.description = description;
}
}

44
common/common-online/src/main/java/apelet/common/online/rule/enums/RuleActionTypeEnum.java

@ -0,0 +1,44 @@
package apelet.common.online.rule.enums;
import lombok.Getter;
import java.util.Arrays;
/**
* 规则动作类型1~8对应规则 JSON allList[].typeRegistry 按它路由到 RuleActionExecutor 实现
*
* 1~4 为数据类动作业务规则 businessRulesList5~8 为控件状态类动作界面规则 conditionTypeList
* <pre>
* 1 计算并赋值 FormulaAssignExecutor 金额 = 数量 * 单价
* 2 携带基础资料 BaseDataCarryExecutor 创建人.fname 提交人
* 3 设置字段值 FieldAssignExecutor 源字段值 目标字段
* 4 清除字段值 FieldClearExecutor 清空字段
* 5~8 锁定/解锁/显示/隐藏控件WidgetLock/Unlock/Show/HideExecutor
* </pre>
*/
@Getter
public enum RuleActionTypeEnum {
FORMULA_ASSIGN(1, "计算并赋值"),
BASE_DATA_CARRY(2, "携带基础资料"),
FIELD_ASSIGN(3, "设置字段值"),
FIELD_CLEAR(4, "清除字段值"),
WIDGET_LOCK(5, "锁定控件"),
WIDGET_UNLOCK(6, "解锁控件"),
WIDGET_SHOW(7, "显示控件"),
WIDGET_HIDE(8, "隐藏控件");
private final int code;
private final String description;
RuleActionTypeEnum(int code, String description) {
this.code = code;
this.description = description;
}
/** 由 widgetJson 动作里的 type 值(1~8)转枚举,未识别抛异常 */
public static RuleActionTypeEnum of(int code) {
return Arrays.stream(values()).filter(t -> t.code == code).findFirst()
.orElseThrow(() -> new IllegalArgumentException("未识别的动作类型: " + code));
}
}

23
common/common-online/src/main/java/apelet/common/online/rule/enums/RuleTypeEnum.java

@ -0,0 +1,23 @@
package apelet.common.online.rule.enums;
import lombok.Getter;
/**
* 规则来源类型界面规则 / 业务规则
* 对应 widgetJson 两个数组conditionTypeList界面规则 / businessRulesList业务规则
* 两个数组在代码里一视同仁处理同一管线ruleType 仅作来源标记供前端差异化展示或排查
*/
public enum RuleTypeEnum {
/** 界面规则:widgetJson 的 conditionTypeList 数组(锁定/解锁/显隐等控件状态类动作) */
INTERFACE("界面规则(conditionTypeList)"),
/** 业务规则:widgetJson 的 businessRulesList 数组(赋值/携带基础资料等数据类动作) */
BUSINESS("业务规则(businessRulesList)");
@Getter
private final String description;
RuleTypeEnum(String description) {
this.description = description;
}
}

32
common/common-online/src/main/java/apelet/common/online/rule/model/FormRule.java

@ -0,0 +1,32 @@
package apelet.common.online.rule.model;
import apelet.common.online.rule.enums.RuleTypeEnum;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 规则定义一条规则 = 一个条件 + 一组动作
*
* 对应 widgetJson businessRulesList / conditionTypeList 数组的一个元素
* <pre>
* {
* "Enable": true, // → enable
* "expressionTranslation": "t_ruletest.t_ruletest.qty NOT NULL && t_ruletest.t_ruletest.prices NOT NULL", // → condition
* "allList": [ { "type": 1, "condition": "1", ... } ] // → actions
* }
* </pre>
* 求值流程条件成立(establish=true) 执行 condition=1 的动作不成立 执行 condition=2 的动作
*/
@Data
public class FormRule {
private RuleTypeEnum ruleType;
private boolean enable = true;
private RuleCondition condition;
private List<RuleAction> actions = new ArrayList<>();
public void addAction(RuleAction action) {
actions.add(action);
}
}

45
common/common-online/src/main/java/apelet/common/online/rule/model/RuleAction.java

@ -0,0 +1,45 @@
package apelet.common.online.rule.model;
import apelet.common.online.rule.enums.EventTypeEnum;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import java.util.List;
/**
* 规则动作一个动作类型一个执行器
* 对应规则 JSON allList 数组里的一个元素
*
* 示例type 1 计算赋值
* <pre>
* {
* "type": 1, // → actionType(FormulaAssignExecutor 处理)
* "condition": "1", // → condition(1=条件成立时执行,2=不成立时执行)
* "checkList": ["加载", "值更新"], // → eventTypes 归一化
* "expressionTranslation": "t_ruletest.t_ruletest.amount = t_ruletest.t_ruletest.qty * t_ruletest.t_ruletest.prices"
* }
* </pre>
*/
@Data
public class RuleAction {
/** 动作类型(1~8),Registry 靠它路由到对应执行器 */
private RuleActionTypeEnum actionType;
/** 1=表达式成立时执行,2=不成立时执行 */
private int condition;
/** 执行顺序(规则内动作按 sort 升序执行,缺失按 0) */
private int sort;
/** checkList 归一化后的事件("加载"→FORM_CREATED,"值更新"/"值改变"→CHANGE,空→ALL) */
private List<EventTypeEnum> eventTypeEnums;
/** 原始动作 JSON(各 type 结构差异大,执行器按需取) */
private JSONObject source;
/** 事件匹配:eventTypes 含 ALL 或含当前事件归一化结果则匹配 */
public boolean matchEvent(String eventCode) {
if (eventTypeEnums == null || eventTypeEnums.contains(EventTypeEnum.ALL)) {
return true;
}
EventTypeEnum normalized = "formCreated".equals(eventCode) ? EventTypeEnum.FORM_CREATED : EventTypeEnum.CHANGE;
return eventTypeEnums.contains(normalized);
}
}

31
common/common-online/src/main/java/apelet/common/online/rule/model/RuleActionContext.java

@ -0,0 +1,31 @@
package apelet.common.online.rule.model;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.model.OnlineForm;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
/**
* 执行上下文执行器用收敛执行器要访问的数据 FormRuleEngine.executeRule 组装后交给执行器
*/
@Getter
@AllArgsConstructor
public class RuleActionContext {
/** 当前表单对象:buildResult 从 form.getWidgetJson() 反查列名 → 控件 variableName(executeAction 无表单时为空) */
private final OnlineForm form;
/** 当前表单数据对象:主表字段 + 分录集合,执行器用 Jexl3Util.resolveFieldChain 从它下钻关联表 */
private final ObjectValue objectValue;
/** 事件插件执行 dto:type2/3 经 getRowKeySubset() 取当前编辑行(executeAction 通用入口为空对象) */
private final OnlineEventPluginExecuteDto dto;
/** 当前动作:执行器从 action.getSource() 取动作参数(specifyList / specifyJiList / expressionTranslation 等) */
private final RuleAction action;
/** 触发事件编码(executeAction 通用入口为 null,不过滤事件) */
private final String eventCode;
/** 触发事件的控件名(分录行指令 buildEntryVo 需要) */
private final String eventWidgetVariableName;
/** 规则级 $函数名$→函数ID 映射(type1 $fun$ 赋值用) */
private final Map<String, Long> functionForm;
}

29
common/common-online/src/main/java/apelet/common/online/rule/model/RuleCondition.java

@ -0,0 +1,29 @@
package apelet.common.online.rule.model;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 规则条件规则级求值用的表达式每条规则只求值一次结果决定走 condition=1 还是 2 的动作
* 对应规则 JSON 顶层的 expressionTranslation / nameList / functionForm
*
* 示例
* <pre>
* expression = "t_ruletest.t_ruletest.qty NOT NULL && t_ruletest.t_ruletest.prices NOT NULL"
* nameList = {"t_ruletest": ["qty", "prices"]}
* functionForm = {"myFun": 123} // 表达式里的 $myFun$ 占位 → 函数ID
* </pre>
*
* 说明nameList / functionForm 仅为兼容旧 JSON 保留求值不依赖ConditionEvaluator 直接扫表达式里的字段引用
*/
@Data
public class RuleCondition {
/** 求值用表达式,真实表名.表名.字段(主表)/ 主表.从表.字段(分录),支持 NOT NULL / == / && 等 */
private String expression;
/** 表达式涉及的字段映射 {"table":["field"]}(保留兼容,求值不读) */
private Map<String, List<String>> nameList;
/** $函数名$ → 函数ID 映射(保留兼容,函数求值走 FunctionExecutor) */
private Map<String, Long> functionForm;
}

19
common/common-online/src/main/java/apelet/common/online/rule/model/RuleEvalContext.java

@ -0,0 +1,19 @@
package apelet.common.online.rule.model;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 求值上下文规则级条件求值用ConditionEvaluator.evaluate 入口
* 当前求值只读 objectValuedto 暂未参与求值分录行的 rowKeySubset 也是从 objectValue 取的
*/
@Getter
@AllArgsConstructor
public class RuleEvalContext {
/** 当前表单数据对象(条件表达式里的字段引用从它解析取值) */
private final ObjectValue objectValue;
/** 预留:暂未参与条件求值 */
private final OnlineEventPluginExecuteDto dto;
}

110
common/common-online/src/main/java/apelet/common/online/rule/parser/FormRuleParser.java

@ -0,0 +1,110 @@
package apelet.common.online.rule.parser;
import apelet.common.online.rule.enums.EventTypeEnum;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.enums.RuleTypeEnum;
import apelet.common.online.rule.model.FormRule;
import apelet.common.online.rule.model.RuleAction;
import apelet.common.online.rule.model.RuleCondition;
import apelet.common.online.util.WidgetJsonUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 解析 widgetJson conditionTypeList / businessRulesList 两数组 List&lt;FormRule&gt;兼容旧 JSON
*/
@Component
public class FormRuleParser {
public List<FormRule> parse(String widgetJson) {
List<FormRule> rules = new ArrayList<>();
if (widgetJson == null) return rules;
rules.addAll(parseArray(WidgetJsonUtil.getRuleArray(widgetJson, "businessRulesList"), RuleTypeEnum.BUSINESS));
rules.addAll(parseArray(WidgetJsonUtil.getRuleArray(widgetJson, "conditionTypeList"), RuleTypeEnum.INTERFACE));
return rules;
}
private List<FormRule> parseArray(JSONArray array, RuleTypeEnum ruleType) {
List<FormRule> rules = new ArrayList<>();
if (array == null) return rules;
for (Object o : array) {
FormRule rule = parseSingle((JSONObject) o, ruleType);
if (rule != null) rules.add(rule);
}
return rules;
}
/** 解析单条规则 JSON(businessRulesList / conditionTypeList 数组元素)→ FormRule;Enable=false 或 JSON 为空返回 null */
public FormRule parseSingle(JSONObject json, RuleTypeEnum ruleType) {
if (json == null || "false".equals(json.getString("Enable"))) return null;
FormRule rule = new FormRule();
rule.setRuleType(ruleType);
rule.setCondition(new RuleCondition());
rule.getCondition().setExpression(json.getString("expressionTranslation"));
rule.getCondition().setNameList(parseNameList(json.getJSONObject("nameList")));
rule.getCondition().setFunctionForm(parseFunctionForm(json.getJSONArray("functionForm")));
JSONArray allList = json.getJSONArray("allList");
if (allList != null) {
for (Object a : allList) {
JSONObject aj = (JSONObject) a;
if (aj == null) continue;
RuleAction action = new RuleAction();
action.setActionType(RuleActionTypeEnum.of(aj.getIntValue("type")));
action.setCondition(aj.getIntValue("condition"));
action.setSort(aj.getIntValue("sort"));
action.setEventTypeEnums(normalizeEvent(aj.getJSONArray("checkList")));
action.setSource(aj);
rule.addAction(action);
}
}
return rule;
}
/** nameList: {"table":["field"]} → Map */
private Map<String, List<String>> parseNameList(JSONObject nameList) {
if (nameList == null) return null;
Map<String, List<String>> map = new HashMap<>();
for (String key : nameList.keySet()) {
map.put(key, nameList.getJSONArray(key).toJavaList(String.class));
}
return map;
}
/** functionForm: [{"funName":id,"str":模板}] → Map&lt;funName,id&gt;(跳过 str 等非数字键) */
private Map<String, Long> parseFunctionForm(JSONArray functionForm) {
if (functionForm == null) return null;
Map<String, Long> map = new HashMap<>();
for (Object o : functionForm) {
JSONObject json = (JSONObject) o;
for (String key : json.keySet()) {
String value = json.getString(key);
if (StringUtils.isNumeric(value)) {
map.put(key, Long.parseLong(value));
}
}
}
return map;
}
/** checkList 归一化:空 → ALL;"加载" → FORM_CREATED;"值更新"/"值改变" → CHANGE */
private List<EventTypeEnum> normalizeEvent(JSONArray checkList) {
List<EventTypeEnum> events = new ArrayList<>();
if (checkList == null || checkList.isEmpty()) {
events.add(EventTypeEnum.ALL);
return events;
}
for (Object o : checkList) {
String event = String.valueOf(o);
if ("加载".equals(event)) events.add(EventTypeEnum.FORM_CREATED);
else events.add(EventTypeEnum.CHANGE);
}
return events;
}
}

21
common/common-online/src/main/java/apelet/common/online/service/IOnlineFormFieldSyncService.java

@ -0,0 +1,21 @@
package apelet.common.online.service;
/**
* 自动同步字段到历史数据
*/
public interface IOnlineFormFieldSyncService {
/**
* 给所有缺少该字段的 onl_form_head 表补充字段可按表类型过滤
* 已同步的表status=1 onl_form_field + syncDB 加物理列 + 回写 zz_online_column
* 未同步的表只补 onl_form_field不同步 DB
*
* @param fieldName 字段名
* @param type 字段类型数据库类型名如 varchar/bigint/decimal onl_fieldType 字典码 0~5
* @param length 字段长度varchar 默认 255decimal 为精度小数位默认 2
* @param description 字段描述
* @param tableType 表类型0 附表 / 1 主表 / 2 单表不传则全部
* @return 同步结果统计
*/
String syncFieldToHistory(String fieldName, String type, Integer length, String description, String tableType);
}

208
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormFieldSyncServiceImpl.java

@ -0,0 +1,208 @@
package apelet.common.online.service.impl;
import apelet.common.core.exception.MyRuntimeException;
import apelet.common.dbutil.object.SqlTableColumn;
import apelet.common.generator.model.OnlFormField;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.generator.service.IOnlFormFieldService;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.CustomUtil;
import apelet.common.online.model.OnlineDatasource;
import apelet.common.online.model.OnlineDblink;
import apelet.common.online.model.OnlineTable;
import apelet.common.online.service.IOnlineFormFieldSyncService;
import apelet.common.online.service.OnlineColumnService;
import apelet.common.online.service.OnlineDblinkService;
import apelet.common.online.service.OnlineDatasourceService;
import apelet.common.online.service.OnlineTableService;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 自动同步字段到历史数据给缺字段的 onl_form_head 表补字段
*/
@Slf4j
@Service
public class OnlineFormFieldSyncServiceImpl implements IOnlineFormFieldSyncService {
/** 数据库类型名 → onl_fieldType 字典码 */
private static final Map<String, String> TYPE_CODE_MAP = new HashMap<>();
static {
TYPE_CODE_MAP.put("varchar", CustomUtil.FieldType_varchar);
TYPE_CODE_MAP.put("nvarchar", CustomUtil.FieldType_varchar);
TYPE_CODE_MAP.put("int", CustomUtil.FieldType_int);
TYPE_CODE_MAP.put("integer", CustomUtil.FieldType_int);
TYPE_CODE_MAP.put("bigint", CustomUtil.FieldType_bigint);
TYPE_CODE_MAP.put("datetime", CustomUtil.FieldType_datetime);
TYPE_CODE_MAP.put("date", CustomUtil.FieldType_datetime);
TYPE_CODE_MAP.put("timestamp", CustomUtil.FieldType_timestamp);
TYPE_CODE_MAP.put("decimal", CustomUtil.FieldType_decimal);
}
@Autowired
private IOnlFormHeadService onlFormHeadService;
@Autowired
private IOnlFormFieldService onlFormFieldService;
@Autowired
private OnlineTableService onlineTableService;
@Autowired
private OnlineColumnService onlineColumnService;
@Autowired
private OnlineDatasourceService onlineDatasourceService;
@Autowired
private OnlineDblinkService onlineDblinkService;
@Autowired
private IdGeneratorWrapper idGenerator;
@Override
public String syncFieldToHistory(String fieldName, String type, Integer length, String description, String tableType) {
if (StringUtils.isBlank(fieldName)) {
throw new MyRuntimeException("字段名不能为空");
}
String fieldType = resolveFieldType(type);
if (fieldType == null) {
throw new MyRuntimeException("不支持的字段类型:" + type);
}
int pageNum = 1;
int pageSize = 100;
int synced = 0;
int skipped = 0;
int failed = 0;
while (true) {
PageMethod.startPage(pageNum, pageSize);
LambdaQueryWrapper<OnlFormHead> wq = new LambdaQueryWrapper<>();
if (StringUtils.isNotEmpty(tableType)) {
wq.eq(OnlFormHead::getTableType, tableType);
}
wq.orderByAsc(OnlFormHead::getId);
Page<OnlFormHead> page = (Page<OnlFormHead>) onlFormHeadService.list(wq);
if (page == null || page.isEmpty()) {
break;
}
for (OnlFormHead head : page) {
int result = syncFieldToHead(head, fieldName, fieldType, length, description);
if (result == 1) {
synced++;
} else if (result == 0) {
skipped++;
} else {
failed++;
}
}
if (page.size() < pageSize) {
break;
}
pageNum++;
}
return "字段同步完成:新增 " + synced + " 张表,跳过(已有该字段)" + skipped + " 张表,失败 " + failed + " 张表";
}
/** 单张表同步:已存在该字段返回 0,同步失败返回 -1,成功返回 1 */
private int syncFieldToHead(OnlFormHead head, String fieldName, String fieldType, Integer length, String description) {
String columnName = fieldName.toLowerCase();
long exists = onlFormFieldService.count(new LambdaQueryWrapper<OnlFormField>()
.eq(OnlFormField::getHeadId, head.getId())
.eq(OnlFormField::getFieldName, columnName));
if (exists > 0) {
return 0;
}
// 长度/小数位:varchar 默认 255;decimal 精度=length、小数位默认 2;datetime/timestamp 无长度
Integer fieldLength = null;
Integer fieldPoint = null;
if (CustomUtil.FieldType_varchar.equals(fieldType)) {
fieldLength = length == null ? 255 : length;
} else if (CustomUtil.FieldType_decimal.equals(fieldType)) {
fieldLength = length;
fieldPoint = 2;
} else if (CustomUtil.FieldType_int.equals(fieldType) || CustomUtil.FieldType_bigint.equals(fieldType)) {
fieldLength = length;
}
// 插入 onl_form_field
OnlFormField field = new OnlFormField();
field.setId(idGenerator.nextLongId());
field.setHeadId(head.getId());
field.setFieldName(columnName);
field.setFieldRemark(description);
field.setFieldType(fieldType);
field.setFieldLength(fieldLength);
field.setFieldPoint(fieldPoint);
field.setIsEmpty(1);
field.setCreateTime(new Date());
onlFormFieldService.save(field);
// 未同步的表:只加字段,不同步 DB
if (!"1".equals(head.getStatus())) {
return 1;
}
// 已同步的表:syncDB 加列 + 回写 zz_online_column
try {
onlFormHeadService.syncDB(head, 0L);
} catch (Exception e) {
log.error("同步表结构失败,headId={},tableName={}", head.getId(), head.getTableName(), e);
return -1;
}
writeOnlineColumn(head, columnName, description);
return 1;
}
/** 回写 zz_online_column:经 datasource → dblink 取新增列信息后 saveBySqlTable */
private void writeOnlineColumn(OnlFormHead head, String columnName, String description) {
OnlineTable onlineTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>()
.eq(OnlineTable::getTableName, head.getTableName()));
if (onlineTable == null) {
return;
}
OnlineDatasource datasource = onlineDatasourceService.getOne(new LambdaQueryWrapper<OnlineDatasource>()
.eq(OnlineDatasource::getMasterTableId, onlineTable.getTableId()));
if (datasource == null) {
return;
}
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
if (dblink == null) {
return;
}
List<SqlTableColumn> columns = onlineDblinkService.getDblinkTableColumnList(dblink, head.getTableName());
if (CollUtil.isEmpty(columns)) {
return;
}
for (SqlTableColumn col : columns) {
if (columnName.equalsIgnoreCase(col.getColumnName())) {
onlineColumnService.saveBySqlTable(col, onlineTable.getTableId(), description);
return;
}
}
}
/** 类型解析:数据库类型名 → onl_fieldType 字典码;直接传字典码则原样使用 */
private String resolveFieldType(String type) {
if (StringUtils.isBlank(type)) {
return null;
}
String code = TYPE_CODE_MAP.get(type.toLowerCase());
if (code != null) {
return code;
}
if (isFieldTypeCode(type)) {
return type;
}
return null;
}
private boolean isFieldTypeCode(String type) {
return CustomUtil.FieldType_varchar.equals(type) || CustomUtil.FieldType_int.equals(type)
|| CustomUtil.FieldType_bigint.equals(type) || CustomUtil.FieldType_datetime.equals(type)
|| CustomUtil.FieldType_decimal.equals(type) || CustomUtil.FieldType_timestamp.equals(type);
}
}

252
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java

@ -16,10 +16,13 @@ import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.model.*; import apelet.common.online.model.*;
import apelet.common.online.model.constant.EventEnum; import apelet.common.online.model.constant.EventEnum;
import apelet.common.online.object.PluginInfo; import apelet.common.online.object.PluginInfo;
import apelet.common.online.rule.FormRuleEngine;
import apelet.common.online.rule.condition.ConditionEvaluator;
import apelet.common.online.rule.condition.FunctionExecutor;
import apelet.common.online.rule.model.RuleCondition;
import apelet.common.online.rule.model.RuleEvalContext;
import apelet.common.online.service.*; import apelet.common.online.service.*;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.util.OnlineRedisKeyUtil; import apelet.common.online.util.OnlineRedisKeyUtil;
import apelet.common.online.vo.SysFunctionVo;
import apelet.common.orm.impl.EntityColumn; import apelet.common.orm.impl.EntityColumn;
import apelet.common.orm.impl.EntityTable; import apelet.common.orm.impl.EntityTable;
import apelet.common.orm.impl.LinkEntityColumn; import apelet.common.orm.impl.LinkEntityColumn;
@ -28,7 +31,6 @@ import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -36,14 +38,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBucket; import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
@ -90,9 +91,11 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
@Autowired @Autowired
private OrmGenDataSourceUtil ormGenDataSourceUtil; private OrmGenDataSourceUtil ormGenDataSourceUtil;
@Autowired @Autowired
ISysFunctionService sysFunctionService; FormRuleEngine formRuleEngine;
@Autowired @Autowired
Jexl3Util jexl3Util; FunctionExecutor functionExecutor;
@Autowired
ConditionEvaluator conditionEvaluator;
/** /**
@ -359,72 +362,14 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
} }
try { try {
JSONObject jsonObject = JSONObject.parseObject(onlineForm.getWidgetJson()); JSONObject jsonObject = JSONObject.parseObject(onlineForm.getWidgetJson());
String deviceType = AppDeviceType.getDeviceType(); JSONObject device = jsonObject.getJSONObject(AppDeviceType.getDeviceType());
JSONArray jsonArray = jsonObject.getJSONObject(deviceType).getJSONArray("pluginList"); ObjectValue objectValue = buildObjectValue(onlineForm, device, onlineEventPluginExecuteDto);
Model model = onlineEventPluginExecuteDto.getModel(); // ② 规则引擎:解析 + 规则级求值 + 执行,直接产出前端指令(不再走插件反射)
Map<String, Object> masterData = model.getMasterData(); pluginResult.addAll(formRuleEngine.execute(onlineForm, objectValue, onlineEventPluginExecuteDto.getEventCode(), onlineEventPluginExecuteDto));
Map<String, Object> savedata = model.getSlaveData(); // ③ 自定义插件原样(只跑 widgetJson.pluginList,不再强制加 MyInterfaceRulesExecute)
// if(masterData == null || savedata == null){ List<PluginInfo> pluginInfos = device == null || device.getJSONArray("pluginList") == null
// return onlineEventPluginExecuteVos; ? new ArrayList<>() : device.getJSONArray("pluginList").toJavaList(PluginInfo.class);
// } pluginResult.addAll(executePlugins(pluginInfos, onlineEventPluginExecuteDto, objectValue));
ObjectValue objectValue = null;
if (masterData != null) {
OnlineDatasource onlineDatasource = onlineDatasourceService.getOnlineDatasourceFromCache(model.getDatasourceId());
objectValue = this.getObjectValue(masterData, savedata, onlineDatasource.getDatasourceName());
objectValue.put("rowKey", onlineEventPluginExecuteDto.getRowKey());
objectValue.put("rowKeySubset", onlineEventPluginExecuteDto.getRowKeySubset());
objectValue.put("rowChange", model.getRowChange());
// 处理条件规则
// 界面规则
JSONArray conditionTypeList = jsonObject.getJSONObject(AppDeviceType.getDeviceType()).getJSONArray("conditionTypeList");
objectValue.put("conditional", this.analysisConditionalRules(conditionTypeList, objectValue));
// 业务规则
JSONArray businessRulesList = jsonObject.getJSONObject(AppDeviceType.getDeviceType()).getJSONArray("businessRulesList");
objectValue.put("business", this.analysisConditionalRules(businessRulesList, objectValue));
objectValue.put("keyNameObj", jsonObject.getJSONObject(AppDeviceType.getDeviceType()).getJSONObject("keyNameObj"));
}else{
objectValue = new ObjectValue(null);
}
objectValue.put("onlineForm",onlineForm);
objectValue.put("listData",model.getListData());
objectValue.put("rowData",model.getRowData());
objectValue.put("eventParams",onlineEventPluginExecuteDto.getEventParams());
objectValue.put("parentParams",onlineEventPluginExecuteDto.getParentParams());
objectValue.put("innerval",onlineEventPluginExecuteDto.getInnerval());
objectValue.put("returnData",model.getReturnData());
List<PluginInfo> pluginInfos = new ArrayList<>();
if (jsonArray != null) {
pluginInfos = jsonArray.toJavaList(PluginInfo.class);
}
PluginInfo pluginInfom = new PluginInfo();
pluginInfom.setPluginMemo("界面规则");
pluginInfom.setPluginType("apelet.common.online.abstractplugin.MyInterfaceRulesExecute");
pluginInfom.setPluginName("MyInterfaceRulesExecute");
pluginInfom.setOrder(-1);
pluginInfos.add(pluginInfom);
pluginInfos.sort(Comparator.comparing(PluginInfo::getOrder));
log.info("eventPluginExecute, pluginInfos=>{}", JSON.toJSONString(pluginInfos));
for (PluginInfo pluginInfo : pluginInfos) {
Class<?> aClass = Class.forName(pluginInfo.getPluginType());
if (onlineEventPluginExecuteDto.getEventCode() == null) {
continue;
}
Method method = aClass.getMethod(EventEnum.of(onlineEventPluginExecuteDto.getEventCode()).getCode(), String.class, ObjectValue.class);
Object object = aClass.newInstance();
Method setDtoMethod = aClass.getMethod("setDto",OnlineEventPluginExecuteDto.class);
setDtoMethod.invoke(object, onlineEventPluginExecuteDto);
method.invoke(object, onlineEventPluginExecuteDto.getEventWidgetVariableName(), objectValue);
Method resultMethod = aClass.getMethod("getResult");
ObjectValue view = (ObjectValue) resultMethod.invoke(object);;
if (view != null) {
List<Object> result = (List<Object>) view.getValues().get("onlineEventPluginExecute");
if (result != null && result.size() > 0) {
pluginResult.addAll(result);
}
}
//break;
}
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException(e);
@ -432,6 +377,90 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
return pluginResult; return pluginResult;
} }
private ObjectValue buildObjectValue(OnlineForm onlineForm, JSONObject device, OnlineEventPluginExecuteDto onlineEventPluginExecuteDto) {
Model model = onlineEventPluginExecuteDto.getModel();
Map<String, Object> masterData = model.getMasterData();
Map<String, Object> savedata = model.getSlaveData();
ObjectValue objectValue;
if (masterData != null) {
OnlineDatasource onlineDatasource = onlineDatasourceService.getOnlineDatasourceFromCache(model.getDatasourceId());
objectValue = this.getObjectValue(masterData, savedata, onlineDatasource.getDatasourceName());
objectValue.put("rowKey", onlineEventPluginExecuteDto.getRowKey());
objectValue.put("rowKeySubset", onlineEventPluginExecuteDto.getRowKeySubset());
objectValue.put("rowChange", model.getRowChange());
objectValue.put("keyNameObj", device == null ? null : device.getJSONObject("keyNameObj"));
} else {
objectValue = new ObjectValue(null);
}
objectValue.put("onlineForm", onlineForm);
objectValue.put("listData", model.getListData());
objectValue.put("rowData", model.getRowData());
objectValue.put("eventParams", onlineEventPluginExecuteDto.getEventParams());
objectValue.put("parentParams", onlineEventPluginExecuteDto.getParentParams());
objectValue.put("innerval", onlineEventPluginExecuteDto.getInnerval());
objectValue.put("returnData", model.getReturnData());
return objectValue;
}
/** 自定义插件反射执行循环,原样保留(不再强制追加 MyInterfaceRulesExecute) */
private List<Object> executePlugins(List<PluginInfo> pluginInfos, OnlineEventPluginExecuteDto onlineEventPluginExecuteDto, ObjectValue objectValue) throws Exception {
List<Object> pluginResult = new ArrayList<>();
for (PluginInfo pluginInfo : pluginInfos) {
if (onlineEventPluginExecuteDto.getEventCode() == null) {
continue;
}
Class<?> aClass = Class.forName(pluginInfo.getPluginType());
Method method = aClass.getMethod(EventEnum.of(onlineEventPluginExecuteDto.getEventCode()).getCode(), String.class, ObjectValue.class);
Object object = aClass.newInstance();
Method setDtoMethod = aClass.getMethod("setDto", OnlineEventPluginExecuteDto.class);
setDtoMethod.invoke(object, onlineEventPluginExecuteDto);
method.invoke(object, onlineEventPluginExecuteDto.getEventWidgetVariableName(), objectValue);
Method resultMethod = aClass.getMethod("getResult");
ObjectValue view = (ObjectValue) resultMethod.invoke(object);
if (view != null) {
List<Object> result = (List<Object>) view.getValues().get("onlineEventPluginExecute");
if (result != null && !result.isEmpty()) {
pluginResult.addAll(result);
}
}
}
return pluginResult;
}
/**
* 保留内置插件Save/Audit/Submit OperationServicePlugIn仍调用委托新 ConditionEvaluator规则级求值
*/
public Object func(String s, JSONObject parsed, ObjectValue objectValue) {
RuleCondition condition = new RuleCondition();
condition.setExpression(s);
condition.setFunctionForm(parsed == null ? null : parseFunctionForm(parsed.getJSONArray("functionForm")));
return conditionEvaluator.evaluate(condition, new RuleEvalContext(objectValue, null));
}
/**
* 保留SaveOperationServicePlugIn / AuditOperationServicePlugIn 等内置插件仍调用委托 FunctionExecutor
*/
public Object executeFunction(JSONObject parsed, String fun, Object[] objects) {
return functionExecutor.executeWithParams(parseFunctionForm(parsed.getJSONArray("functionForm")), fun, objects);
}
private Map<String, Long> parseFunctionForm(JSONArray functionForm) {
Map<String, Long> map = new HashMap<>();
if (functionForm == null) {
return map;
}
for (Object o : functionForm) {
JSONObject json = (JSONObject) o;
for (String key : json.keySet()) {
String value = json.getString(key);
if (StringUtils.isNumeric(value)) {
map.put(key, Long.parseLong(value));
}
}
}
return map;
}
@Override @Override
public ObjectValue getObjectValue(Map<String, Object> masterData, Map<String, Object> savedata, String tableName) { public ObjectValue getObjectValue(Map<String, Object> masterData, Map<String, Object> savedata, String tableName) {
EntityTable entityTable = ormGenDataSourceUtil.getEntityTable(tableName); EntityTable entityTable = ormGenDataSourceUtil.getEntityTable(tableName);
@ -524,81 +553,4 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
} }
} }
/**
* 处理 条件/业务 规则
*/
public List<Map<String, Object>> analysisConditionalRules(JSONArray jsonArray, ObjectValue objectValue) {
List<Map<String, Object>> list = new ArrayList<>();
// 获取页面处理规则
if (CollUtil.isEmpty(jsonArray)) {
return list;
}
for (Object o : jsonArray) {
Map<String, Object> hashMap = new HashMap<>();
JSONObject parsed = JSON.parseObject(o.toString());
if (parsed.getString("Enable").equals("false")) {
continue;
}
String s = parsed.getString("expressionTranslation");
// 规则表达式
Object result = func(s, parsed, objectValue);
hashMap.put("establish", result);
// 表达式包含的字段属性名称
if (ObjectUtils.isEmpty(parsed.get("nameList"))) {
break;
}
hashMap.put("functionForm", parsed.getJSONArray("functionForm"));
hashMap.put("expression", s);
// JSONObject jsonObject1 = JSON.parseObject(parsed.get("nameList").toString());
// Map<String, Object> mapValue = getMapValue(jsonObject1, objectValue);
// hashMap.put("establish", established(s, mapValue));
hashMap.put("allList", parsed.getJSONArray("allList"));
list.add(hashMap);
}
return list;
}
public Object func(String s, JSONObject parsed, ObjectValue objectValue) {
List<Map<String, Object>> alist = new ArrayList<>();
jexl3Util.splitExpression(s, alist);
boolean result = true;
for (Map<String, Object> map : alist) {
String expression = map.get("expression").toString();
if (expression.startsWith("$")) {
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", expression);
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(","));
Object[] objects = jexl3Util.getFunParam(funParam, objectValue);
//执行函数
Object o = executeFunction(parsed, fun, objects);
result = jexl3Util.judgmentFunc(expression, o) && result;
} else {
// 表达式
boolean b = jexl3Util.getExpressionValue(expression, objectValue);
result = b && result;
}
}
return result;
}
// 执行函数
public Object executeFunction(JSONObject parsed, String fun, Object[] objects) {
JSONArray array = parsed.getJSONArray("functionForm");
String finalFun = fun.substring(0, fun.indexOf('(')).trim();
Long funId = 0L;
for (Object object : array) {
JSONObject json = (JSONObject) object;
if (!json.containsKey(finalFun)) {
continue;
}
funId = (json.getLong(finalFun));
}
SysFunction sysFunction = sysFunctionService.getById(funId);
SysFunctionVo sysFunctionVo = new SysFunctionVo();
BeanUtils.copyProperties(sysFunction, sysFunctionVo);
sysFunctionVo.setParameter(objects);
return sysFunctionService.onLineFunction(sysFunctionVo);
}
} }

81
common/common-online/src/main/java/apelet/common/online/util/Jexl3Util.java

@ -1,9 +1,7 @@
package apelet.common.online.util; package apelet.common.online.util;
import apelet.common.core.constant.AppDeviceType;
import apelet.common.core.object.ObjectCollection; import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue; import apelet.common.core.object.ObjectValue;
import apelet.common.core.object.TokenData;
import apelet.common.generator.utils.OrmGenDataSourceUtil; import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.orm.impl.Filter; import apelet.common.orm.impl.Filter;
import apelet.common.orm.impl.FilterItem; import apelet.common.orm.impl.FilterItem;
@ -42,9 +40,7 @@ public class Jexl3Util {
* @return * @return
*/ */
public Object expression(String expressionStr, Map<String, Object> paramMap) { public Object expression(String expressionStr, Map<String, Object> paramMap) {
expressionStr = expressionStr.toLowerCase() expressionStr = expressionStr.toLowerCase().replaceAll("\\bnot\\b", " != ").replaceAll("\\bnull\\b", "''");
.replace("not", " != ")
.replace("null", "''");
// 创建表达式引擎对象 // 创建表达式引擎对象
JexlEngine engine = new JexlBuilder().create(); JexlEngine engine = new JexlBuilder().create();
// 创建Context对象,为表达式中的未知数赋值 // 创建Context对象,为表达式中的未知数赋值
@ -73,6 +69,7 @@ public class Jexl3Util {
map.put(tab + "." + tab + "." + object, v); map.put(tab + "." + tab + "." + object, v);
} else { } else {
ObjectCollection o1 = (ObjectCollection) objectValue.get(tab); ObjectCollection o1 = (ObjectCollection) objectValue.get(tab);
if (o1 == null) continue; // 附表为空,跳过
Iterator iterator = o1.iterator(); Iterator iterator = o1.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next(); ObjectValue next = (ObjectValue) iterator.next();
@ -112,6 +109,7 @@ public class Jexl3Util {
} else { } else {
List<Object> objects = new ArrayList<>(); List<Object> objects = new ArrayList<>();
ObjectCollection o = (ObjectCollection) objectValue.get(strings[1]); ObjectCollection o = (ObjectCollection) objectValue.get(strings[1]);
if (o == null) return Collections.emptyList(); // 分录表为空,返回空参数
Iterator iterator = o.iterator(); Iterator iterator = o.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next(); ObjectValue next = (ObjectValue) iterator.next();
@ -231,6 +229,79 @@ public class Jexl3Util {
return ormGenDataSourceUtil.query(table, filter, null).getObject(0); return ormGenDataSourceUtil.query(table, filter, null).getObject(0);
} }
/**
* 当前编辑行rowKeySubset 命中的行未过滤时返回无 rowkey 的新增行都没有则取第一行
*/
public ObjectValue currentRow(ObjectCollection collection, String rowKeySubset) {
if (collection == null || collection.isEmpty()) return null;
Set<String> targetSet = new HashSet<>();
if (StringUtils.isNotEmpty(rowKeySubset)) Collections.addAll(targetSet, rowKeySubset.split(","));
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
if ((StringUtils.isEmpty(rowKeySubset) && next.get("rowkey") != null)
|| (next.get("rowkey") != null && !targetSet.contains(next.get("rowkey")))) continue;
return next;
}
return null;
}
/**
* 当前编辑分录行rowKeySubset 命中行内编辑多行优先其次主对象 rowKey 命中分录编辑完成事件rowKey=当前编辑行都无则返回无 rowkey 的新增行
*/
public ObjectValue currentRow(ObjectCollection collection, String rowKeySubset, String rowKey) {
if (collection == null || collection.isEmpty()) return null;
if (StringUtils.isNotEmpty(rowKeySubset)) {
ObjectValue row = currentRow(collection, rowKeySubset);
if (row != null) return row;
}
if (StringUtils.isNotEmpty(rowKey)) {
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
if (rowKey.equals(next.getString("rowkey"))) return next;
}
}
return currentRow(collection, null);
}
/**
* 灵活解析字段引用到实际值
* 支持 ..字段 / .字段 / .关联对象.子字段主表主表.分录表.字段 / 主表.分录表.关联对象.子字段分录取当前编辑行
* 关联对象(ObjectValue)自动下钻任意深度
*/
public Object resolveRefValue(String ref, ObjectValue objectValue, String rowKeySubset) {
String[] parts = ref.split("\\.");
if (parts.length < 2) return "";
boolean masterPrefix = parts[0].equals(parts[1]);
int start = masterPrefix ? 2 : 1;
if (start >= parts.length) return "";
Object v = objectValue.get(parts[start]);
if (!masterPrefix && v instanceof ObjectCollection) {
ObjectValue row = currentRow((ObjectCollection) v, rowKeySubset, (String) objectValue.get("rowKey"));
return row == null ? "" : resolveFieldChain(row, parts, start + 1);
}
return resolveFieldChain(objectValue, parts, start);
}
/**
* 从给定对象起按字段链逐段解析遇到 ObjectValue关联表弹窗自动下钻任意深度最后一段返回实际值
*/
public Object resolveFieldChain(ObjectValue current, String[] parts, int from) {
ObjectValue cur = current;
for (int i = from; i < parts.length; i++) {
if (cur == null) return "";
Object v = cur.get(parts[i]);
if (i == parts.length - 1) return v == null ? "" : v;
if (v instanceof ObjectValue) {
cur = getObjectValue(((ObjectValue) v).get("id"), ((ObjectValue) v).getTableName());
} else {
return v == null ? "" : v;
}
}
return "";
}
public boolean judgmentFunc(String expression, Object value) { public boolean judgmentFunc(String expression, Object value) {
String trim = expression.substring(expression.indexOf(")") + 1).trim(); String trim = expression.substring(expression.indexOf(")") + 1).trim();
trim = "functionValue" + " " + trim.substring(0, trim.length() - 1); trim = "functionValue" + " " + trim.substring(0, trim.length() - 1);

51
common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java

@ -9,8 +9,10 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import jodd.util.StringUtil; import jodd.util.StringUtil;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -199,6 +201,55 @@ public class WidgetJsonUtil {
} }
/** /**
* 收集 pc/mobile 控件树中 列名(columnFieldName) 控件 variableName 列表
* 仅收集 pc/mobile widgetList叶子字段控件递归 childWidgetList 下钻 buildTableFieldMap 取值一致
* 一个列名可对应多个控件多个控件绑定同一列时返回多个 variableName
*
* @param widgetJson 在线表单的 widgetJson
* @return 列名 variableName 列表
*/
public static Map<String, List<String>> collectColumnVariableNames(JSONObject widgetJson) {
Map<String, List<String>> map = new HashMap<>();
if (widgetJson == null) return map;
for (String mode : new String[]{"pc", "mobile"}) {
JSONObject modeObj = widgetJson.getJSONObject(mode);
if (modeObj == null) continue;
collectColumnVariableNames(modeObj.getJSONArray("widgetList"), map);
}
return map;
}
private static void collectColumnVariableNames(JSONArray widgetList, Map<String, List<String>> map) {
if (CollUtil.isEmpty(widgetList)) {
return;
}
for (int i = 0; i < widgetList.size(); i++) {
JSONObject widget = widgetList.getJSONObject(i);
if (widget == null) {
continue;
}
JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) {
collectColumnVariableNames(childWidgetList, map);
continue;
}
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) {
continue;
}
String columnFieldName = bindData.getString("columnFieldName");
String variableName = widget.getString("variableName");
if (StrUtil.isEmpty(columnFieldName) || StrUtil.isEmpty(variableName)) {
continue;
}
List<String> names = map.computeIfAbsent(columnFieldName, k -> new ArrayList<>());
if (!names.contains(variableName)) {
names.add(variableName);
}
}
}
/**
* 递归收集控件树中的"表-字段"绑定关系 * 递归收集控件树中的"表-字段"绑定关系
*/ */
private static void collectTableFields(JSONArray widgetList, Map<String, Map<String, JSONObject>> tableFieldMap) { private static void collectTableFields(JSONArray widgetList, Map<String, Map<String, JSONObject>> tableFieldMap) {

Loading…
Cancel
Save