Aviator 规则引擎 是一款轻量级、高性能的 Java 表达式求值引擎,专注于解决动态规则执行和表达式计算问题,它可以运用在如电商促销规则(满减、折扣)、风控策略(用户评分模型)等场景。
困扰:
在过去的项目中,我多次使用规则引擎来处理不同的业务场景。每个项目的规则需求各不相同,前端传递的 JSON 数据格式也多种多样。为了应对这些差异,我不得不为每种规则单独编写解析方法,将前端的 JSON 数据转换为对应的规则公式。为了解决这一问题,我设计了一个通用组件,用于统一前端 JSON 数据的格式,并在后端实现了一套通用的解析逻辑。该组件具有高度的可扩展性和复用性,能够适应不同项目的规则需求,同时减少了重复开发的工作量。
PS: 组件代码处处都重要,篇幅过长,我主要介绍将规则实体解析成公式的部分。
规则 的核心由条件 和动作两部分组成。首先,需要明确触发规则的条件,即满足何种逻辑时规则会被命中;其次,在规则命中后,需定义执行的具体动作。同时,还需考虑规则未命中时的处理逻辑,以确保规则引擎在不同场景下均能做出合理的响应。
组件整体架构
以下是我统一的规则实体
规则实体
js
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Rule {
@Schema(description = "规则ID")
private String id;
@Schema(description = "规则表达式")
private String expression;
@Schema(description = "条件列表")
private List<Condition> conditions;
@Schema(description = "命中后的动作")
private Action action;
@Schema(description = "未命中的动作")
private Action fallbackAction;
// @Schema(description = "规则优先级")
// private int priority;
//
// @Schema(description = "规则是否有效")
// private boolean validity;
}
条件实体
js
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Condition {
@Schema(description = "字段名")
private String field;
@Schema(description = "操作符") // 可以是 '==', '>', '<' 等
private String operator;
@Schema(description = "值")
private List<String> value;
@Schema(description = "自定义函数")
private String customFunction;
@Schema(description = "布尔逻辑") // 可以是 "&&" 或 "||"
private String logic;
@Schema(description = "前缀")
private String prefix;
@Schema(description = "值是否被引号包围")
private Boolean isValueQuoted = false;
@Schema(description = "子条件")
private List<Condition> subConditions;
@Schema(description = "算术表达式链")
private List<ArithmeticExpression> arithmeticExpressions;
@Schema(description = "是否需要括号包围")
private boolean isBracketed;
@Schema(description = "表达式,存在则直接运用,不解析")
private String Expression;
@Schema(description = "命中后的动作")
private Action action;
动作实体
js
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Action {
@Schema(description = "动作类型")
private String type;
@Schema(description = "动作目标")
private Object target;
@Schema(description = "目标类型")
private Class<?> targetType;
}
算术表达式链(为了满足复杂公式的解析)
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ArithmeticExpression {
@Schema(description = "算术操作符,例如 + 或 -,可以为null表示第一个操作数")
private String operator;
@Schema(description = "操作数,例如 A 或 B")
private String operand;
public ArithmeticExpression(String operand) {
this.operator = null;
this.operand = operand;
}
}
规则构造测试
简单加减乘除,与或非
算术表达式链,括号的运用
自定义函数的使用
规则实体解析成公式的方法
java
/**
* 获取完整表达式
*
* @param rule
* @return
*/
public static String buildStatement(Rule rule) {
String expression;
if (ObjUtil.isNotNull(rule.getExpression()) && !rule.getExpression().isEmpty()) {
// 有传表达式,则直接使用,不解析
expression = rule.getExpression();
} else {
// 解析
expression = buildExpression(rule.getConditions());
}
if (Boolean.TRUE.equals(rule.getIsMontageAction())) {
if (Boolean.TRUE.equals(rule.getIsDegree())) {
// 计算匹配度
int i = calculateMinConditions(expression);
return String.format("if (%s) { return %s; } else { return %s; }", expression, i, -1);
} else {
return String.format("if (%s) { return %s; } else { return %s; }",
expression,
rule.getAction().getTarget().toString(),
rule.getFallbackAction().getTarget().toString());
}
} else {
return expression;
}
}
构建规则表达式
java
/**
* 构建规则表达式
*
* @param conditions 条件
* @return 表达式
*/
public static String buildExpression(List<Condition> conditions) {
StringBuilder expressionBuilder = new StringBuilder();
// 判断是否存在action有值的condition
long actionNum = conditions.stream().filter(condition -> ObjUtil.isNotEmpty(condition.getAction())).count();
if (actionNum > 0) {
expressionBuilder.append("if (");
}
for (int i = 0; i < conditions.size(); i++) {
Condition condition = conditions.get(i);
if (ObjUtil.isNotEmpty(condition.getExpression())) {
expressionBuilder.append("(").append(condition.getExpression()).append(")");
if (ObjUtil.isNotEmpty(condition.getLogic())) {
expressionBuilder.append(" ").append(condition.getLogic()).append(" ");
}
} else {
// 如果需要括号,添加左括号
if (condition.isBracketed()) {
expressionBuilder.append("(");
}
// 处理子条件
if (condition.getSubConditions() != null && !condition.getSubConditions().isEmpty()) {
expressionBuilder.append(buildExpression(condition.getSubConditions()));
}
// 动态拼接前缀
String prefixedField = condition.getPrefix() != null ? condition.getPrefix() + condition.getField() : condition.getField();
// 处理算术表达式或单一条件
if (condition.getArithmeticExpressions() != null && !condition.getArithmeticExpressions().isEmpty()) {
for (int j = 0; j < condition.getArithmeticExpressions().size(); j++) {
ArithmeticExpression arithmeticExpression = condition.getArithmeticExpressions().get(j);
// 如果是第一个操作数,直接添加
if (j > 0 && arithmeticExpression.getOperator() != null) {
expressionBuilder.append(" ").append(arithmeticExpression.getOperator()).append(" ");
}
expressionBuilder.append(arithmeticExpression.getOperand());
}
// 添加比较运算符和值
expressionBuilder.append(" ")
.append(condition.getOperator())
.append(" ")
.append(condition.getIsValueQuoted() ? "'" + String.join(", ", condition.getValue()) + "'" : String.join(", ", condition.getValue()));
} else if (condition.getField() != null) {
if (condition.getCustomFunction() != null && !condition.getCustomFunction().isEmpty()) {
// 使用自定义函数,确保格式为 field operator function(value)
expressionBuilder.append(prefixedField)
.append(" ")
.append(condition.getOperator())
.append(" ")
.append(condition.getCustomFunction())
.append("(")
.append(condition.getIsValueQuoted() ? "'" + String.join(", ", condition.getValue()) + "'" : String.join(", ", condition.getValue()))
.append(")");
} else {
// 普通的操作符逻辑
expressionBuilder.append(prefixedField)
.append(" ")
.append(condition.getOperator())
.append(" ")
.append(condition.getIsValueQuoted() ? "'" + String.join(", ", condition.getValue()) + "'" : String.join(", ", condition.getValue()));
}
}
// 如果需要括号,添加右括号
if (condition.isBracketed()) {
expressionBuilder.append(")");
}
// 添加逻辑操作符
if (i < conditions.size() - 1 && condition.getLogic() != null) {
expressionBuilder.append(" ").append(condition.getLogic()).append(" ");
}
}
// 添加逻辑操作符
if (i < conditions.size() - 1) {
Condition nextCondition = conditions.get(i + 1);
if (nextCondition.getAction() != null) {
expressionBuilder.append(")")
.append(" { return ")
.append(nextCondition.getAction().getTargetType() == Boolean.class ? nextCondition.getAction().getTarget() : "'" + nextCondition.getAction().getTarget() + "'")
.append("; }")
.append(" elsif (");
}
}
}
String result;
if (expressionBuilder.charAt(expressionBuilder.length() - 1) == '(' && expressionBuilder.length() > 8) {
result = expressionBuilder.substring(0, expressionBuilder.length() - 8);
} else {
result = expressionBuilder.toString().trim();
}
return result;
}
实际运用页面图(并未开放复杂规则配置,前端优化一下即可,后台都可以解析)
前端传的规则实体:
js
{
"id": "rule",
"expression": "",
"conditions": [
{
"field": "",
"operator": "",
"value": [
"aircraftType",
"'NARROW;WIDE'"
],
"customFunction": "intersect",
"logic": "&&",
"prefix": "",
"isValueQuoted": false,
"bracketed": false,
"valueType": "dict",
"optionalValue": "AircraftType"
},
{
"field": "",
"operator": "",
"value": [
"firstClassVipPaxNum",
"'121;33'"
],
"customFunction": "intersect",
"logic": "&&",
"prefix": "",
"isValueQuoted": false,
"bracketed": false,
"valueType": "",
"optionalValue": ""
},
{
"field": "firstClassVipPaxNum",
"operator": ">",
"value": [
"'1212'"
],
"customFunction": "",
"logic": "&&",
"prefix": "",
"isValueQuoted": false,
"bracketed": false,
"valueType": "",
"optionalValue": ""
},
{
"field": "",
"operator": "",
"value": [
"airlineIcaoCode",
"'A320'"
],
"customFunction": "string.endsWith",
"logic": "",
"prefix": "",
"isValueQuoted": false,
"bracketed": false,
"valueType": "baseAirport",
"optionalValue": "airline"
}
],
"isMontageAction": true,
"isDegree": true
}
实际转换成的表达式:
js
if (intersect(aircraftType, 'NARROW;WIDE') && intersect(firstClassVipPaxNum, '121;33') && firstClassVipPaxNum > '1212' && string.endsWith(airlineIcaoCode, 'A320')) { return 3; } else { return -1; }
因为最少需要三个参数都命中才可为true,所以匹配度是3(组件里有计算的方法),否则返回 -1,我一个收费项可以配置多个规则,最后返回命中的匹配度最高的规则的计算公式的值。
计算结果(支持批量计算)
总结:
由于开发时间有限,组件的实现可能不够简洁和优雅,但其核心目的是为大家提供一个清晰的思路,帮助大家更好地理解规则引擎的原理及其应用场景。希望通过这个组件,能够启发大家在规则引擎的使用上探索更多可能性。同时,也欢迎大家在此基础上进一步完善和优化,共同打造更加高效、优雅的解决方案。