zoukankan      html  css  js  c++  java
  • 通用的规则匹配算法(原创)(java+.net)

    1.java里可以使用Spring的 Spel或者Google的Aviator

    如果使用 Aviator 则添加以下依赖

          <dependency>
              <groupId>com.googlecode.aviator</groupId>
              <artifactId>aviator</artifactId>
              <version>4.1.2</version>
          </dependency>

    不过,推荐使用Spel

    一般的规则匹配最终都会采用如下表达式来计算

    如   ( {status} in "2,3" && ({level} in "p1,p2" || {times} in "1,9"))

    但是存储在DB中一般采用 List<Model>的方式来存储,这样方便管理界面的前端的渲染 (当然也不排除直接存储表达式的,不过前端的渲染就有些难度了)

    整个解析过程实现过程有以下几步

    1.存储的List中的规则转换为表达式 

       1.1 增加括号

       1.2 替换变量

       1.3 构造spel表达式

       1.4 连接下一个规则

    2.计算表达式

    代码如下:

    import com.google.common.collect.ImmutableMap;
    
    
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    @NoArgsConstructor
        @AllArgsConstructor
        @Data
        static class RuleItem {
            /**
             * 左变量
             */
            private String left;
    
            /**
             * 比较表达式
             */
            private ComparelOpration comparelOpration;
    
            /**
             * 右变量或者常量
             */
            private String right;
    
            /**
             * 连接下一个表达式的逻辑运算符
             */
            private LogicalOpration logicalOpra;
        }
    
    
        @NoArgsConstructor
        @AllArgsConstructor
        @Data
        static class RuleModel {
            /**
             * 规则列表
             */
            private List<RuleItem> ruleItems;
    
            /**
             * 左括号放在第几个Item之前
             */
            private List<Integer> leftParenthesesIndex;
    
            /**
             * 右括号放在第几个Item之后
             */
            private List<Integer> rightParenthesesIndex;
        }
    
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        static class SpelResult {
            private String express;
            private StandardEvaluationContext context;
        }

    使用的两个连接器(比较连接和逻辑连接)

    enum ComparelOpration {
            In,
            NotIn,
            GreaterThan,
            LessThan,
            GreaterEqualThan,
            LessEqualThan,
            Equal,
            NotEqual;
    
            public static boolean isDecimalCompareLogicalOpration(ComparelOpration opration) {
                return opration.ordinal() == ComparelOpration.GreaterThan.ordinal()
                        || opration.ordinal() == ComparelOpration.GreaterEqualThan.ordinal()
                        || opration.ordinal() == ComparelOpration.LessEqualThan.ordinal()
                        || opration.ordinal() == ComparelOpration.LessThan.ordinal();
            }
    
            public static boolean isEqualLogicalOpration(ComparelOpration opration) {
                return opration.ordinal() == ComparelOpration.Equal.ordinal()
                        || opration.ordinal() == ComparelOpration.NotEqual.ordinal()
                        ;
            }
        }
    
        enum LogicalOpration {
            None,
            And,
            Or;
    
            static String toStr(LogicalOpration logicalOpration) {
                return logicalOpration.ordinal() == LogicalOpration.None.ordinal()
                        ? ""
                        : (logicalOpration.ordinal() == LogicalOpration.And.ordinal() ? "&&" : "||");
            }
        }

    匹配工厂如下

     static class SpelMatchFactory {
            private static final ExpressionParser parser = new SpelExpressionParser();
    
            static SpelResult toSpelExpress(RuleModel model, Map<String, String> userFeature) {
                List<RuleItem> ruleItemList = model.getRuleItems();
                StringBuilder sb = new StringBuilder();
                StandardEvaluationContext ctx = new StandardEvaluationContext();
                for (int i = 0; i < ruleItemList.size(); i++) {
                    RuleItem item = ruleItemList.get(i);
                    if (model.leftParenthesesIndex.contains(i)) {
                        sb.append("(");
                    }
    
                    String listKey = "list" + i;
                    String valueKey = "item" + i;
    
                    String subExpress = compute(item, listKey, valueKey);
                    sb.append(subExpress);
    
                    String leftValue = item.getLeft();
                    if (leftValue.startsWith("{") && leftValue.endsWith("}")) {
                        leftValue = userFeature.get(leftValue.substring(1, leftValue.length() - 1));
                    }
    
                    String rightValue = item.getRight();
                    if (rightValue.startsWith("{") && rightValue.endsWith("}")) {
                        rightValue = userFeature.get(rightValue.substring(1, rightValue.length() - 1));
                    }
    
                    if (ComparelOpration.isDecimalCompareLogicalOpration(item.comparelOpration)) {
                        ctx.setVariable(listKey, Integer.parseInt(rightValue));
                        ctx.setVariable(valueKey, Integer.parseInt(leftValue));
                    } else if (ComparelOpration.isEqualLogicalOpration(item.comparelOpration)) {
                        ctx.setVariable(listKey, rightValue);
                        ctx.setVariable(valueKey, leftValue);
                    } else {
                        ctx.setVariable(listKey, Arrays.asList(rightValue.split(",")));
                        ctx.setVariable(valueKey, leftValue);
                    }
    
                    if (model.rightParenthesesIndex.contains(i)) {
                        sb.append(")");
                    }
    
                    if (item.logicalOpra.ordinal() != LogicalOpration.None.ordinal()) {
                        sb.append(LogicalOpration.toStr(item.getLogicalOpra()));
                    }
                }
    
                return new SpelResult(sb.toString(), ctx);
            }
    
            public static boolean compute(RuleModel model, Map<String, String> userFeature) {
                SpelResult spelExpressResult = SpelMatchFactory.toSpelExpress(model, userFeature);
    
                Boolean execResult = parser.parseExpression(spelExpressResult.getExpress()).getValue(
                        spelExpressResult.getContext(),
                        Boolean.class);
                return execResult;
            }
    
            private static String compute(RuleItem matchItem, String listKey, String valueKey) {
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.Equal.ordinal()) {
                    return "#" + listKey + ".equals(#" + valueKey + ")";
                }
    
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.NotEqual.ordinal()) {
                    return "!#" + listKey + ".equals(#" + valueKey + ")";
                }
    
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.In.ordinal()) {
                    return "#" + listKey + ".contains(#" + valueKey + ")";
                }
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.NotIn.ordinal()) {
                    return "!#" + listKey + ".contains(#" + valueKey + ")";
                }
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.GreaterEqualThan.ordinal()) {
                    return "#" + valueKey + ">=" + "#" + listKey;
                }
    
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.LessEqualThan.ordinal()) {
                    return "#" + valueKey + "<=" + "#" + listKey;
                }
    
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.GreaterThan.ordinal()) {
                    return "#" + valueKey + ">" + "#" + listKey;
                }
    
                if (matchItem.getComparelOpration().ordinal() == ComparelOpration.LessThan.ordinal()) {
                    return "#" + valueKey + "<" + "#" + listKey;
                }
    
                throw new IllegalArgumentException("不支持的逻辑运算类型");
            }
        }

    最后 ,测试代码如下:

    public static void main(String[] args) {
            List<RuleItem> ruleItems = new ArrayList<>();
            ruleItems.add(new RuleItem("{status}", ComparelOpration.In, "2,3", LogicalOpration.Or));
            ruleItems.add(new RuleItem("{level}", ComparelOpration.In, "1,2", LogicalOpration.And));
            ruleItems.add(new RuleItem("{hours}", ComparelOpration.GreaterEqualThan, "48", LogicalOpration.And));
            ruleItems.add(new RuleItem("{phone1}", ComparelOpration.Equal, "{phone2}", LogicalOpration.None));
            RuleModel model = new RuleModel();
            model.setRuleItems(ruleItems);
    
            //左括号在0的位置之前
            model.setLeftParenthesesIndex(Arrays.asList(0));
            //右括号在1的位置之后
            model.setRightParenthesesIndex(Arrays.asList(1));
           //以上表达式相当于 ({status} in '2,3' or {level} in '1,2') && {hours}>=48 && {phone1}=={phone2}
    
            //1. {phone1} != {phone2} ,结果为false
            Map<String, String> userFeature1 = ImmutableMap.of("status", "2", "level", "1", "phone1",
                    "13900000000", "phone2", "13900000001", "hours", "66");
            boolean computeResult = SpelMatchFactory.compute(model, userFeature1);
            System.out.println("userFeature1的匹配结果:" + computeResult);
    
    
            //2.{hours} < 48 ,结果为false
            Map<String, String> userFeature2 = ImmutableMap.of("status", "2", "level", "1", "phone1",
                    "13900000000", "phone2", "13900000001", "hours", "6");
            computeResult = SpelMatchFactory.compute(model, userFeature2);
            System.out.println("userFeature2的匹配结果:" + computeResult);
    
    
            //3. {status} 不在 2,3 中,但是 level 在 1,2中,结果为true
            Map<String, String> userFeature3 = ImmutableMap.of("status", "1", "level", "1", "phone1",
                    "13900000000", "phone2", "13900000000", "hours", "66");
            computeResult = SpelMatchFactory.compute(model, userFeature3);
            System.out.println("userFeature3的匹配结果:" + computeResult);
    
            //4. {status} 不在 2,3 中,且 level 不在 1,2中,结果为false
            Map<String, String> userFeature4 = ImmutableMap.of("status", "1", "level", "3", "phone1",
                    "13900000000", "phone2", "13900000000", "hours", "66");
            computeResult = SpelMatchFactory.compute(model, userFeature4);
            System.out.println("userFeature4的匹配结果:" + computeResult);
    
            //4.一切都匹配,返回true
            Map<String, String> userFeature5 = ImmutableMap.of("status", "2", "level", "1", "phone1",
                    "13900000000", "phone2", "13900000000", "hours", "66");
            computeResult = SpelMatchFactory.compute(model, userFeature5);
            System.out.println("userFeature5的匹配结果:" + computeResult);
        }

    输出结果为:

    表达式:(#list0.contains(#item0)||#list1.contains(#item1))&&#item2>=#list2&&#list3.equals(#item3)
    userFeature1的匹配结果:false
    表达式:(#list0.contains(#item0)||#list1.contains(#item1))&&#item2>=#list2&&#list3.equals(#item3)
    userFeature2的匹配结果:false
    表达式:(#list0.contains(#item0)||#list1.contains(#item1))&&#item2>=#list2&&#list3.equals(#item3)
    userFeature3的匹配结果:true
    表达式:(#list0.contains(#item0)||#list1.contains(#item1))&&#item2>=#list2&&#list3.equals(#item3)
    userFeature4的匹配结果:false
    表达式:(#list0.contains(#item0)||#list1.contains(#item1))&&#item2>=#list2&&#list3.equals(#item3)
    userFeature5的匹配结果:true

    c#.net的代码如下

    c#.net使用 ExpressionEvaluator.2.0.4.0 来做表达式的计算

  • 相关阅读:
    雅虎军规34条 (一)
    jetty和tomcat的区别
    Jsp--9大内置对象
    java 重定向和转发的区别
    layer弹出层
    html 锚点
    css绘制三角形
    原生js下拉菜单联动
    layui省市区下拉菜单三级联动
    tp5时间格式转换
  • 原文地址:https://www.cnblogs.com/zhshlimi/p/11249785.html
Copyright © 2011-2022 走看看