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 來做表達式的計算