记录一个Jexl规则引擎表达式生成工具类

4 阅读2分钟

最近写了两个项目,都需要根据一定的规则,匹配数据库记录然后给对应的用户打分或者打标签。 最终选择了 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 "
 }
  • 区间

就是数学上的区间 一共四种情况

  1. 左开右开: (10, 20)
  2. 左开右闭: (10, 20]
  3. 左闭右开: [10, 20)
  4. 左闭右闭: [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) {}
}