QLExpress案例

wen java案例 2

本文目录导读:

QLExpress案例

  1. 案例一:基础入门(Maven 依赖 & Hello World)
  2. 案例二:业务逻辑场景(电商价格计算)
  3. 案例三:规则引擎实战(风控系统)
  4. 案例四:高级特性(函数定义 & 宏 & 依赖注入)
  5. 案例五:性能优化与安全(编译缓存 & 沙箱)
  6. 总结:QLExpress 适用场景与最佳实践

QLExpress 是阿里巴巴开源的一款轻量级、高性能的规则引擎,主要用于解决业务规则复杂多变、灵活配置的问题,它采用 Java 语法(近似)编写,支持脚本化动态编译执行。

下面我将通过几个从简单到复杂的典型案例,展示 QLExpress 的核心用法、语法特性和实际业务场景。


基础入门(Maven 依赖 & Hello World)

这是最基础的用法,演示如何引入依赖并执行一个简单表达式。

引入 Maven 依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>QLExpress</artifactId>
    <version>3.3.1</version> <!-- 请使用最新稳定版本 -->
</dependency>

基础执行代码

import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
public class HelloWorld {
    public static void main(String[] args) throws Exception {
        ExpressRunner runner = new ExpressRunner();
        DefaultContext<String, Object> context = new DefaultContext<String, Object>();
        // 执行简单的数学运算
        Object r1 = runner.execute("1 + 2 * 3", context, null, true, false);
        System.out.println("Result: " + r1); // 输出 7
        // 带变量的表达式
        context.put("a", 10);
        context.put("b", 5);
        Object r2 = runner.execute("a > b ? 'a更大' : 'b更大'", context, null, true, false);
        System.out.println("Result: " + r2); // 输出 a更大
    }
}

业务逻辑场景(电商价格计算)

这是最常见的业务场景——根据规则计算商品价格,包含条件分支、循环和方法调用。

业务规则

  • 普通会员:原价 9 折
  • VIP 会员:原价 7 折,且满 500 减 50
  • 如果商品库存不足,提示错误
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
public class PriceCalculator {
    // 定义一个业务方法,供表达式调用
    public static double calculateDiscount(int level, double price) {
        if (level == 1) return price * 0.9;
        if (level == 2) return price * 0.7;
        return price;
    }
    public static void main(String[] args) throws Exception {
        ExpressRunner runner = new ExpressRunner();
        // 将 Java 方法注入到表达式中
        runner.addFunction("calcDiscount", new com.ql.util.express.Operator() {
            @Override
            public Object executeInner(Object[] list) throws Exception {
                int level = (Integer) list[0];
                double price = (Double) list[1];
                return calculateDiscount(level, price);
            }
        });
        DefaultContext<String, Object> context = new DefaultContext<String, Object>();
        context.put("memberLevel", 2);   // VIP
        context.put("goodsPrice", 800.0);
        context.put("stock", 10);
        // 复杂表达式:包含条件、方法调用
        String expression = 
            "if (stock < 1) { return '库存不足'; }" +
            "else { " +
            "   double finalPrice = calcDiscount(memberLevel, goodsPrice);" +
            "   if (memberLevel == 2 && finalPrice >= 500) {" +
            "        finalPrice = finalPrice - 50;" +
            "   }" +
            "   return '应付金额: ' + finalPrice;" +
            "}";
        Object result = runner.execute(expression, context, null, true, false);
        System.out.println(result); // 输出: 应付金额: 510.0 (800*0.7 - 50)
    }
}

规则引擎实战(风控系统)

这是 QLExpress 最核心的使用场景——动态下发风控规则,无需重启应用即可快速调整规则。

场景:根据用户行为(登录地域、金额、频次)判断是否存在风险。

核心代码(规则加载与执行)

package com.example.rule;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class RiskControlEngine {
    // 缓存规则,key为规则ID,value为规则表达式
    private static Map<String, String> ruleCache = new ConcurrentHashMap<>();
    private ExpressRunner runner = new ExpressRunner();
    // 动态更新规则(通过后台管理系统调用)
    public void updateRule(String ruleId, String expression) {
        ruleCache.put(ruleId, expression);
        System.out.println("规则已更新: " + ruleId);
    }
    // 执行风控检查
    public boolean checkRisk(String ruleId, Map<String, Object> params) throws Exception {
        String expression = ruleCache.get(ruleId);
        if (expression == null) {
            throw new IllegalArgumentException("规则不存在: " + ruleId);
        }
        DefaultContext<String, Object> context = new DefaultContext<>();
        // 将请求参数注入上下文
        params.forEach(context::put);
        Object result = runner.execute(expression, context, null, true, false);
        // 结果转为 boolean
        return result instanceof Boolean && (Boolean) result;
    }
    public static void main(String[] args) throws Exception {
        RiskControlEngine engine = new RiskControlEngine();
        // 初始规则:如果交易金额 > 5000 且 新用户 且 IP 不在白名单,则风控
        String rule1 = 
            "amount > 5000 && isNewUser && !whiteList.contains(ip) " +
            "&& (loginCount < 3 || lastLoginDays > 30)";
        engine.updateRule("RULE_HIGH_TRADE", rule1);
        // 模拟请求参数
        DefaultContext<String, Object> params1 = new DefaultContext<>();
        params1.put("amount", 12000);
        params1.put("isNewUser", true);
        params1.put("ip", "192.168.1.100");
        params1.put("loginCount", 2);
        params1.put("lastLoginDays", 45);
        params1.put("whiteList", java.util.Arrays.asList("127.0.0.1", "10.0.0.1"));
        boolean risk1 = engine.checkRisk("RULE_HIGH_TRADE", params1);
        System.out.println("是否触发风控: " + risk1); // 输出 true (金额大、新用户、登录少且间隔久)
        // 动态修改规则(不重启应用)
        String rule1New = 
            "amount > 10000 && isNewUser && !whiteList.contains(ip) " +
            "&& (loginCount < 5 || lastLoginDays > 15)";
        engine.updateRule("RULE_HIGH_TRADE", rule1New);
        // 再次检测,规则已更新
        boolean risk2 = engine.checkRisk("RULE_HIGH_TRADE", params1);
        System.out.println("更新规则后是否触发风控: " + risk2); 
        // 因为 loginCount(2) < 5 依然为true,所以仍为 true
        // 模拟一个业务人员调低阈值
        DefaultContext<String, Object> params2 = new DefaultContext<>();
        params2.put("amount", 8000);
        params2.put("isNewUser", false);
        params2.put("ip", "10.0.0.5");
        params2.put("loginCount", 10);
        params2.put("lastLoginDays", 2);
        params2.put("whiteList", java.util.Arrays.asList("127.0.0.1"));
        boolean risk3 = engine.checkRisk("RULE_HIGH_TRADE", params2);
        System.out.println("低风险用户是否触发: " + risk3); // 输出 false (金额未超10000 且 非新用户)
    }
}

高级特性(函数定义 & 宏 & 依赖注入)

QLExpress 支持在脚本中定义函数(function)、使用宏(macro)以及通过 Spring 注入 Bean,以下展示其强大扩展能力。

场景:物流运费计算 + 优惠券逻辑。

import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
public class AdvancedUsage {
    public static void main(String[] args) throws Exception {
        ExpressRunner runner = new ExpressRunner();
        // 1. 定义一个函数:计算运费(按重量阶梯计价)
        runner.addFunction("calcFreight", new com.ql.util.express.Operator() {
            @Override
            public Object executeInner(Object[] list) throws Exception {
                double weight = (Double) list[0];
                double region = (Double) list[1]; // 地区系数
                double base = (Double) list[2];
                // 模拟:首重 1kg 内 base,超出后每kg 加 1.5
                if (weight <= 1.0) {
                    return base * region;
                } else {
                    return (base + (weight - 1.0) * 1.5) * region;
                }
            }
        });
        // 2. 定义宏(常量替换)
        runner.addMacro("FREE_SHIPPING_THRESHOLD", 199.0);
        runner.addMacro("VIP_DISCOUNT", 0.85);
        // 3. 模拟 Spring Bean 注入(假设有个优惠券服务)
        @SuppressWarnings("unused")
        class CouponService {
            public double getCouponAmount(double price, int couponId) {
                return couponId == 1001 ? 20.0 : (couponId == 1002 ? 50.0 : 0.0);
            }
        }
        Object couponBean = new CouponService() {
            public Object executeInner(Object[] list) {
                // 简化:直接暴露一个方法
                return (Double) getCouponAmount((Double) list[0], (Integer) list[1]);
            }
        };
        runner.addFunction("getCoupon", new com.ql.util.express.Operator() {
            @Override
            public Object executeInner(Object[] list) throws Exception {
                return ((CouponService) list[0]).getCouponAmount((Double) list[1], (Integer) list[2]);
            }
        });
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.put("weight", 3.5);
        context.put("region", 1.2); // 偏远地区
        context.put("prodPrice", 250.0);
        context.put("couponId", 1001);
        context.put("couponService", couponBean);
        // 复杂表达式:使用宏、函数、条件
        String expression = 
            // 如果商品价格达到免邮门槛(199),运费为0,否则计算运费
            "freight = prodPrice >= FREE_SHIPPING_THRESHOLD ? 0.0 : calcFreight(weight, region, 10);" +
            // 应用 VIP 折扣和优惠券(注意:这里演示宏和函数)
            "finalPrice = (prodPrice * VIP_DISCOUNT) - getCoupon(couponService, prodPrice, couponId);" +
            // 自动取到变量结果
            "total = finalPrice + freight;" +
            "return '原价: ' + prodPrice + ', 折后: ' + finalPrice + ', 运费: ' + freight + ', 总价: ' + total;";
        Object result = runner.execute(expression, context, null, true, false);
        System.out.println(result);
        // 输出: 原价: 250.0, 折后: 192.5, 运费: 0.0, 总价: 192.5
    }
}

性能优化与安全(编译缓存 & 沙箱)

在生产环境中,我们通常希望提前编译表达式限制脚本能力以保证性能和安全。

import com.ql.util.express.ExpressRunner;
import com.ql.util.express.DefaultContext;
import java.util.ArrayList;
import java.util.List;
public class ProductionReady {
    public static void main(String[] args) throws Exception {
        // 开启沙箱模式,禁止某些危险操作(如 System.exit, Runtime.exec 等)
        ExpressRunner runner = new ExpressRunner(true, true);
        // 预编译表达式(提高性能)
        String expression = "name == 'Alice' ? '欢迎' : '拒绝'";
        com.ql.util.express.InstructionSet instructionSet = runner.parseInstructionSet(expression);
        // 多线程池化使用
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int num = i;
            Thread t = new Thread(() -> {
                try {
                    DefaultContext<String, Object> context = new DefaultContext<>();
                    context.put("name", num % 2 == 0 ? "Alice" : "Bob");
                    // 使用编译好的指令集,避免重复解析
                    Object result = runner.executeInstructionSet(instructionSet, context, null);
                    System.out.println(Thread.currentThread().getName() + ": " + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads.add(t);
            t.start();
        }
        for (Thread t : threads) t.join();
    }
}

QLExpress 适用场景与最佳实践

场景 是否推荐 说明
风控规则 ✅ 强烈推荐 规则变更频繁,动态下发
营销活动(改折扣/满减) ✅ 推荐 业务人员可配置
复杂计算引擎 ✅ 推荐 如运费、积分、佣金计算
数据校验 ✅ 可选 支持复杂条件
简单 if-else 映射 ❌ 不推荐 直接用 Java 或枚举更简单
超高性能要求(QPS万级以上) ⚠️ 谨慎 虽然支持预编译,但脚本执行仍有开销

最佳实践贴士

  1. 预编译:在启动时将常用规则 parseInstructionSet,不要每次执行都解析。
  2. 白名单:在脚本中限制可调用方法,禁止 RuntimeSystemClass 等危险类。
  3. 上下文隔离:每次请求传入独立的 DefaultContext,避免并发数据污染。
  4. 监控:添加执行耗时日志和异常统计。

通过以上案例,你已经掌握了 QLExpress 从基础语法到生产级应用的核心技巧,希望你项目开发顺利!如果还有细节想了解(比如特定版本差异、SPEL 对比),可以继续交流。

抱歉,评论功能暂时关闭!