基于 QLExpress 规则引擎
这里不讨论 QLExpress的使用,只基于 QLExpress 构建自己的规则描述方案。
基础实现 规则描述模型 Rule -> Group -> Condition
Rule
Group
String andor
List conditions
Condition
dataCode 数据标识
valueType 数据类型
compare 对比逻辑
greater (>)
less (<)
equal (==)
not_equal (!=)
greater_equal (>=)
less_equal (<=)
not_null (!=null)
is_null (==null)
like ( like )
thresholdValueType 阈值类型
thresholdCode 阈值标识
thresholdArithmetic 阈值运算符
thresholdValue 阈值
name
actionDesc
enablePeriod [] 生效小时实现一天内部分规则定时执行
比如描述
1 A类违规天数90天内 == false and (虚假交易扣分 < 48 or 假冒扣分 <= 12) and 待整改卖家 < 总数*0.8 and (宝贝相符DSR > 4.6 or 职位名称 like %开发%)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 private Rule getRule () { Rule.RuleCondition condition1= new Rule .RuleCondition(); condition1.setName("A类违规天数" ); condition1.setCode("A类违规天数90天内" ); condition1.setCompare(OperatorEnum.EQUAL.getSymbol()); condition1.setThresholdValueType(RuleValueTypeEnum.normal.name()); condition1.setThresholdValue("false" ); Rule.RuleGroup group1 = new Rule .RuleGroup(); group1.setConditions(Arrays.asList(condition1)); Rule.RuleCondition condition2= new Rule .RuleCondition(); condition2.setName("虚假交易扣分" ); condition2.setCode("虚假交易扣分" ); condition2.setCompare(OperatorEnum.LESS.getSymbol()); condition2.setThresholdValueType(RuleValueTypeEnum.normal.name()); condition2.setThresholdValue("48" ); Rule.RuleCondition condition3= new Rule .RuleCondition(); condition3.setName("假冒扣分" ); condition3.setCode("假冒扣分" ); condition3.setCompare(OperatorEnum.LESS_EQUAL.getSymbol()); condition3.setThresholdValueType(RuleValueTypeEnum.normal.name()); condition3.setThresholdValue("12" ); Rule.RuleGroup group2 = new Rule .RuleGroup(); group2.setAndor(LogicalRelationEnum.or.name()); group2.setConditions(Arrays.asList(condition2,condition3)); Rule.RuleCondition condition4= new Rule .RuleCondition(); condition4.setName("待整改卖家" ); condition4.setCode("待整改卖家" ); condition4.setCompare(OperatorEnum.LESS.getSymbol()); condition4.setThresholdValueType(RuleValueTypeEnum.data_code.name()); condition4.setThresholdCode("总数" ); condition4.setThresholdArithmetic("*" ); condition4.setThresholdValue("0.8" ); Rule.RuleGroup group3 = new Rule .RuleGroup(); group3.setConditions(Arrays.asList(condition4)); Rule.RuleCondition condition5= new Rule .RuleCondition(); condition5.setName("宝贝相符DSR" ); condition5.setCode("宝贝相符DSR" ); condition5.setCompare(OperatorEnum.GREATER.getSymbol()); condition5.setThresholdValueType(RuleValueTypeEnum.normal.name()); condition5.setThresholdValue("4.6" ); Rule.RuleCondition condition6= new Rule .RuleCondition(); condition6.setName("职位名称" ); condition6.setCode("职位名称" ); condition6.setCompare(OperatorEnum.LIKE.getSymbol()); condition6.setThresholdValueType(RuleValueTypeEnum.normal.name()); condition6.setThresholdValue("'%开发%'" ); Rule.RuleGroup group4 = new Rule .RuleGroup(); group4.setAndor(LogicalRelationEnum.or.name()); group4.setConditions(Arrays.asList(condition5,condition6)); Rule rule = new Rule (); rule.setAndor(LogicalRelationEnum.and.name()); rule.setGroups(Arrays.asList(group1,group2,group3,group4)); return rule; }
执行规则逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void testRunRule () throws Exception { String expression = getRule().getExpression(); System.out.println(expression); DefaultContext<String, Object> context = new DefaultContext <String, Object>(); context.put("A类违规天数90天内" , false ); context.put("虚假交易扣分" , 49 ); context.put("假冒扣分" , 11 ); context.put("待整改卖家" , 70 ); context.put("总数" , 100 ); context.put("宝贝相符DSR" , 4.0 ); context.put("职位名称" , "开发小组" ); List<String> errorList = new ArrayList <>(); boolean result = (Boolean)getRunner().execute(expression, context, errorList, true , false ); System.out.println("result :" + result); System.out.println("errorList :" + JSON.toJSONString(errorList)); }
输出
1 2 3 4 A类违规天数90天内 == false and (虚假交易扣分 < 48 or 假冒扣分 <= 12) and 待整改卖家 < (总数*0.8) and (宝贝相符DSR > 4.6 or 职位名称 like '%开发%') result :true errorList :[" 虚假交易扣分:49 < 48 "," 宝贝相符DSR:4.0 > 4.6 "]
这里加上Runner的初始化
1 2 3 4 5 6 7 8 9 10 11 12 private ExpressRunner getRunner () { ExpressRunner expressRunner = new ExpressRunner (true , false ); expressRunner.getOperatorFactory().getOperator("<" ).setErrorInfo("$1<$2" ); expressRunner.getOperatorFactory().getOperator(">" ).setErrorInfo("$1>$2" ); expressRunner.getOperatorFactory().getOperator("<=" ).setErrorInfo("$1<=$2" ); expressRunner.getOperatorFactory().getOperator(">=" ).setErrorInfo("$1>=$2" ); expressRunner.getOperatorFactory().getOperator("!=" ).setErrorInfo("$1!=$2" ); expressRunner.getOperatorFactory().getOperator("<>" ).setErrorInfo("$1<>$2" ); expressRunner.getOperatorFactory().getOperator("==" ).setErrorInfo("$1==$2" ); expressRunner.getOperatorFactory().getOperator("like" ).setErrorInfo("$1like$2" ); return expressRunner; }
20220812更新 原版本不支持字符串的比较, 比如
1 2 3 4 5 6 Rule.RuleCondition condition3= new Rule .RuleCondition(); condition3.setName("职位名称" ); condition3.setCode("职位名称" ); condition3.setCompare(OperatorEnum.EQUAL.getSymbol()); condition3.setThresholdValueType(RuleValueTypeEnum.normal.name()); condition3.setThresholdValue("开发小组" );
这种情况下, 拼接的表达式逻辑是 职位名称 == 开发小组 导致 QlExpress不能正确解析两边的内容(应该是把 开发小组 没有当成值)。 所以这里需要明确指定 开发小组 是变量,增加双引号。
RuleCondition类中增加对Value值的处理逻辑,目前没有好的方案,只是判断值是否是数值,不是数值的话都转成字符串(注意这种情况其实也会影响true/false的判断逻辑,所以true/false默认还是设置成String类型比较)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 @Getter @Setter public static class RuleCondition { private String code; private String valueType; private String compare; private String thresholdValueType; private String thresholdCode; private String thresholdArithmetic; private String thresholdValue; private String name ; private List<String> enablePeriod; public String getExpression () { RuleValueTypeEnum typeEnum = RuleValueTypeEnum.of(thresholdValueType); if (typeEnum == null ){ throw new RuntimeException ("invalid thresholdValueType" ); } StringBuilder express = new StringBuilder (); express.append(code).append(" " ).append(compare).append(" " ); if (RuleValueTypeEnum.normal == typeEnum){ express.append(generateNormalValue(thresholdValue)); }else if (RuleValueTypeEnum.data_code == typeEnum){ if (thresholdArithmetic != null && thresholdValue != null ){ express.append("(" ).append(thresholdCode) .append(thresholdArithmetic) .append(thresholdValue) .append(")" ); }else { express.append(thresholdCode); } } return express.toString(); } private String generateNormalValue (String thresholdValue) { boolean isNumber = NumberUtils.isCreatable(thresholdValue); if (isNumber){ return thresholdValue; } return "\"" + thresholdValue + "\"" ; } }