通用的規則匹配算法(原創)(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 來做表達式的計算

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章