Browse Source
- 在OnlFormHeadServiceImpl中添加seq字段用于排序顺序控制 - 新增AbstractRuleActionExecutor基类提供Vo产出助手功能 - 实现BaseDataCarryExecutor支持携带基础资料功能 - 新增ConditionEvaluator实现规则条件求值逻辑 - 在EventEnum中添加SCRIPT_EVENT脚本事件类型 - 定义EventTypeEnum归一化事件类型枚举 - 扩展ExecutePluginParent添加scriptEvent方法 - 实现FieldAssignExecutor支持设置字段值功能 - 新增FieldClearExecutor实现清除字段值功能 - 创建FormRule模型定义规则结构 - 构建FormRuleEngine作为规则引擎核心入口 - 实现FormRuleParser解析规则配置 - 开发FormulaAssignExecutor处理计算赋值逻辑feature/2026-07-ccc-dev
34 changed files with 1938 additions and 467 deletions
@ -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; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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 为 null,checkList 只作展示,不拦截执行)。 |
||||||
|
* |
||||||
|
* @param ruleJson 单条规则 JSON 串(businessRulesList / conditionTypeList 数组元素:expressionTranslation、allList、Enable 等) |
||||||
|
* @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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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 锁定控件:普通控件 → DISABLED;btnId 特例(锁父容器内按钮)→ 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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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 优先 getById;ID 缺失/查不到时按函数名或编码(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); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
package apelet.common.online.rule.enums; |
||||||
|
|
||||||
|
import lombok.Getter; |
||||||
|
|
||||||
|
/** |
||||||
|
* 归一化后的事件类型,由动作 checkList 归一化而来: |
||||||
|
* "加载" → FORM_CREATED;"值更新"/"值改变" → CHANGE;空数组 → ALL(监听全部事件)。 |
||||||
|
* |
||||||
|
* 触发匹配:eventCode = formCreated 命中 FORM_CREATED,其余命中 CHANGE;ALL 恒命中。 |
||||||
|
*/ |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
package apelet.common.online.rule.enums; |
||||||
|
|
||||||
|
import lombok.Getter; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
|
/** |
||||||
|
* 规则动作类型(1~8),对应规则 JSON allList[].type,Registry 按它路由到 RuleActionExecutor 实现。 |
||||||
|
* |
||||||
|
* 1~4 为数据类动作(业务规则 businessRulesList),5~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)); |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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 入口)。 |
||||||
|
* 当前求值只读 objectValue;dto 暂未参与求值(分录行的 rowKeySubset 也是从 objectValue 取的)。 |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@AllArgsConstructor |
||||||
|
public class RuleEvalContext { |
||||||
|
/** 当前表单数据对象(条件表达式里的字段引用从它解析取值) */ |
||||||
|
private final ObjectValue objectValue; |
||||||
|
/** 预留:暂未参与条件求值 */ |
||||||
|
private final OnlineEventPluginExecuteDto dto; |
||||||
|
} |
||||||
@ -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<FormRule>(兼容旧 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<funName,id>(跳过 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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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 默认 255;decimal 为精度、小数位默认 2) |
||||||
|
* @param description 字段描述 |
||||||
|
* @param tableType 表类型(0 附表 / 1 主表 / 2 单表,不传则全部) |
||||||
|
* @return 同步结果统计 |
||||||
|
*/ |
||||||
|
String syncFieldToHistory(String fieldName, String type, Integer length, String description, String tableType); |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue