最近写了两个项目,都需要根据一定的规则,匹配数据库记录然后给对应的用户打分或者打标签。 最终选择了 jexl 规则引擎。表达式通过自己创建的枚举和工具类来生成。
如果不想用 apache 的可以直接使用 Hutool 也挺方便的
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.2.1</version>
</dependency>
1. 创建比较枚举
这里边的乘法,是因为我有个需求每次加减5分,所以需要将数据库数据乘上一个数
@Getter
@AllArgsConstructor
public enum ConditionEnum {
CONTAINS("包含", "contains", "{}.contains('{}')"),
NOT_CONTAINS("不包含", "notContains", "!{}.contains('{}')"),
GT("大于", "gt", "{} > {}"),
GE("大于等于", "gte", "{} >= {}"),
EQ("等于", "eq", "{} == {}"),
LT("小于", "lt", "{} < {}"),
LE("小于等于", "lte", "{} <= {}"),
NE("不等于", "ne", "{} != {}"),
RANGE("范围", "range", "{} {} {} && {} {} {}"),
MULTIPLY("乘法", "multiply", "{} * {}"),
TERNARY("三元表达式", "ternary", "{} ? {} : {}"),
DEFAULT("默认", "default", "true")
;
public static ConditionEnum getBySymbol(String containsSymbol) {
for (ConditionEnum value : values()) {
if (StrUtil.equalsIgnoreCase(value.getContainsSymbol(), containsSymbol)) {
return value;
}
}
return DEFAULT;
}
/**
* 描述
*/
private final String describe;
/**
* 操作符
*/
public final String containsSymbol;
/**
* 格式化字符串
*/
private final String formatStr;
}
2. 创建工具类生成表达式
根据前端传入的比较对象,比较值 来生成对应的表达式。
其中 三元表达式 和 区间有些特殊。
- 三元
数据库的 credit_flag 存储 bit(1) 用来表示 true false 最终表达式就是 credit_flag ? 5 : 0
{
"businessParam": "creditFlag",
"conditionSymbol": "ternary",
"conditionValue": " 5 : 0 "
}
- 区间
就是数学上的区间 一共四种情况
- 左开右开:
(10, 20)
- 左开右闭:
(10, 20]
- 左闭右开:
[10, 20)
- 左闭右闭:
[10, 20]
public final class ConditionUtil {
/**
* 简化表达式
* 将 true\s*&& 或者 &&\s*true 部分,包括任意数量的空格 替换为空
*
* @param expression 需要转换的表达式
* @return String 简化后的表达式
*/
public static String simplifyExpression(String expression) {
// 去除 &&true 部分,包括任意数量的空格
return expression.replaceAll("&&\s*true", "")
// 去除 true&& 部分,包括任意数量的空格
.replaceAll("true\s*&&", "")
// 去除多余空格
.replaceAll("\s+", " ")
.trim();
}
public static String jexlExpression(ScoreItemRule itemRule) {
return simplifyExpression(getCondition(itemRule.getParam(),
itemRule.getConditionSymbol(),
itemRule.getConditionVal()));
}
/**
* 处理jexl字符
*
* @param businessParam 参数
* @param conditionSymbol 操作符
* @param conditionValue 条件值
* @return
*/
public static String getCondition(String businessParam, String conditionSymbol, Object conditionValue) {
if (StrUtil.isBlank(conditionSymbol)) {
return ConditionEnum.DEFAULT.getFormatStr();
}
switch (ConditionEnum.getBySymbol(conditionSymbol)) {
case CONTAINS:
return StrUtil.format(ConditionEnum.CONTAINS.getFormatStr(), businessParam, conditionValue);
case NOT_CONTAINS:
return StrUtil.format(ConditionEnum.NOT_CONTAINS.getFormatStr(), businessParam, conditionValue);
case GT:
return StrUtil.format(ConditionEnum.GT.getFormatStr(), businessParam, conditionValue);
case GE:
return StrUtil.format(ConditionEnum.GE.getFormatStr(), businessParam, conditionValue);
case EQ:
return StrUtil.format(ConditionEnum.EQ.getFormatStr(), businessParam, conditionValue);
case LT:
return StrUtil.format(ConditionEnum.LT.getFormatStr(), businessParam, conditionValue);
case LE:
return StrUtil.format(ConditionEnum.LE.getFormatStr(), businessParam, conditionValue);
case NE:
return StrUtil.format(ConditionEnum.NE.getFormatStr(), businessParam, conditionValue);
case RANGE:
String[] range = conditionValue.toString().split(",");
String leftSymbol = range[0].trim().startsWith("[") ? ">=" : ">";
String rightSymbol = range[1].trim().endsWith("]") ? "<=" : "<";
return StrUtil.format(ConditionEnum.RANGE.getFormatStr(),
businessParam, leftSymbol, range[0].replaceAll("[(\[]", ""),
businessParam, rightSymbol, range[1].replaceAll("[])]", ""));
case MULTIPLY:
return StrUtil.format(ConditionEnum.MULTIPLY.getFormatStr(), businessParam, conditionValue);
case TERNARY:
String[] split = String.valueOf(conditionValue).split(":");
return StrUtil.format(ConditionEnum.TERNARY.getFormatStr(), businessParam, split[0], split[1]);
default:
return ConditionEnum.DEFAULT.getFormatStr();
}
}
public static void main(String[] args) {
String condition = "(1,2)";
System.out.println(getCondition("pass_rate", "range", condition));
}
}
3. 处理表达式获取结果
针对 三元 和乘法 我这里特殊处理
Object evaluate;
try {
evaluate = jexlEngine.createExpression(rule.getJexlExpression())
.evaluate(new MapContext(evaluationEntry));
} catch (Exception e) {
log.error("jexl表达式解析错误 {}", evaluationEntry);
continue;
}
ConditionEnum anEnum = ConditionEnum.getBySymbol(rule.getConditionSymbol());
if (anEnum == ConditionEnum.MULTIPLY || anEnum == ConditionEnum.TERNARY) {
}else {
if ((boolean) evaluate) {}
}