基于SpringBoot + QLExpress打造动态规则引擎

前言

作为一名后端开发,你是否遇到过这样的场景:

  • 运营同学说:"双11活动规则临时调整,满300减60改成满200减50,能马上上线吗?"
  • 产品经理说:"风控策略需要微调一下,这个规则能不能今晚就生效?"
  • 老板说:"竞品降价了,我们的VIP折扣也要调整,最好现在就能改!"

每次遇到这种情况,我们都要:修改代码 → 重新打包 → 发布部署 → 重启服务。这个流程短则半小时,长则几个小时,错过最佳时机不说,还要承担发布风险。

今天,我们来聊聊如何用动态规则引擎彻底解决这个痛点!

业务痛点:规则变更的"老大难"问题

传统方式的困境

在传统的业务系统中,业务规则通常硬编码在业务逻辑中:

java 复制代码
// 传统的硬编码方式
public BigDecimal calculateVipDiscount(String userLevel, BigDecimal price) {
    if ("GOLD".equals(userLevel)) {
        return price.multiply(new BigDecimal("0.8")); // 8折
    } else if ("SILVER".equals(userLevel)) {
        return price.multiply(new BigDecimal("0.9")); // 9折
    }
    return price; // 无折扣
}

这种方式存在诸多问题:

  1. 响应慢:规则变更需要完整的开发流程
  2. 风险高:每次发布都可能影响整个系统
  3. 成本大:需要开发、测试、运维多方配合
  4. 不灵活:无法快速响应市场变化

真实的业务场景

电商行业

  • 每逢大促活动,优惠规则频繁调整
  • A/B测试需要不同的定价策略
  • 竞品价格变动需要快速跟进

金融行业

  • 风控模型需要根据市场情况实时调整
  • 利率政策变化需要快速响应
  • 审核规则需要根据业务发展动态优化

内容平台

  • 推荐算法需要不断优化
  • 内容审核规则需要及时更新
  • 用户等级体系需要灵活调整

解决方案:动态规则引擎

核心思路

动态规则引擎的本质是将业务规则业务代码分离:

  • 业务代码:负责流程控制和数据处理

  • 业务规则:以脚本形式独立存储,运行时动态执行

    传统方式:业务逻辑 = 流程控制 + 业务规则(硬编码)
    动态规则:业务逻辑 = 流程控制 + 业务规则(脚本化)

技术选型

本演示DEMO我选择了以下技术栈:

  • 规则引擎:QLExpress - 阿里开源
  • 后端框架:Spring Boot - 快速开发,生态完善
  • 前端技术:HTML5 + TailwindCSS

核心实现:手把手搭建动态规则引擎

1. 项目结构设计

csharp 复制代码
springboot-dynamic-rule/
├── entity/
│   ├── RuleScript.java              # 规则实体
│   ├── OrderProcessResult.java      # 订单处理结果
│   └── RuleExecuteResponse.java     # 规则执行响应
├── service/
│   ├── DynamicRuleEngine.java       # 规则引擎核心
│   └── OrderService.java            # 业务服务
├── controller/
│   ├── RuleController.java          # 规则管理API
│   └── OrderController.java         # 业务API
└── resources/
    └── static/
        ├── index.html               # 规则管理页面
        └── business.html            # 业务演示页面

2. 规则引擎核心实现

java 复制代码
@Slf4j
@Service
public class DynamicRuleEngine {

    // 内存存储规则,实际项目可使用数据库
    private final Map<String, RuleScript> ruleCache = new ConcurrentHashMap<>();
    private final ExpressRunner expressRunner = new ExpressRunner();

    @PostConstruct
    public void init() {
        initDefaultRules();
    }

    private void initDefaultRules() {
        // VIP折扣规则
        addRule(new RuleScript("vip_discount",
            "if (userLevel == \"GOLD\") { return price * 0.8; } " +
            "else if (userLevel == \"SILVER\") { return price * 0.9; } " +
            "else { return price; }",
            "VIP用户折扣规则"));

        // 满减活动规则
        addRule(new RuleScript("full_reduction",
            "if (totalAmount >= 200) { return totalAmount - 50; } " +
            "else if (totalAmount >= 100) { return totalAmount - 20; } " +
            "else { return totalAmount; }",
            "满减活动规则"));
    }

    public RuleExecuteResponse executeRule(String ruleName, Map<String, Object> params) {
        try {
            RuleScript rule = ruleCache.get(ruleName);
            if (rule == null || !rule.isEnabled()) {
                return RuleExecuteResponse.error("规则不存在或已禁用: " + ruleName);
            }

            DefaultContext<String, Object> context = new DefaultContext<>();
            if (params != null) {
                params.forEach(context::put);
            }

            Object result = expressRunner.execute(rule.getScript(), context, null, true, false);
            log.info("执行规则: {}, 结果: {}", ruleName, result);

            return RuleExecuteResponse.success(result);
        } catch (Exception e) {
            log.error("执行规则失败: {}", ruleName, e);
            return RuleExecuteResponse.error("执行失败: " + e.getMessage());
        }
    }
}

3. 业务服务集成

java 复制代码
@Service
@RequiredArgsConstructor
public class OrderService {

    private final DynamicRuleEngine ruleEngine;

    public OrderProcessResult processOrder(Order order) {
        List<OrderProcessResult.ProcessStep> steps = new ArrayList<>();
        BigDecimal currentAmount = order.getOriginalAmount();

        // 1. 应用VIP折扣规则
        BigDecimal discountedAmount = applyVipDiscount(order, currentAmount, steps);

        // 2. 应用满减规则
        BigDecimal finalAmount = applyFullReduction(order, discountedAmount, steps);

        // 3. 计算积分奖励
        Integer points = calculatePoints(finalAmount, order);

        return new OrderProcessResult(/* 构建返回结果 */);
    }

    private BigDecimal applyVipDiscount(Order order, BigDecimal currentAmount,
                                       List<OrderProcessResult.ProcessStep> steps) {
        Map<String, Object> params = new HashMap<>();
        params.put("userLevel", order.getUserLevel());
        params.put("price", currentAmount);

        RuleExecuteResponse response = ruleEngine.executeRule("vip_discount", params);

        if (response.isSuccess()) {
            BigDecimal result = new BigDecimal(response.getResult().toString());
            BigDecimal reduction = currentAmount.subtract(result);

            // 记录处理步骤
            steps.add(new OrderProcessResult.ProcessStep(
                "VIP折扣",
                "根据用户等级 " + order.getUserLevel() + " 应用折扣",
                currentAmount, result, reduction, "vip_discount"
            ));

            return result;
        }

        return currentAmount;
    }
}

4. REST API接口

java 复制代码
@RestController
@RequestMapping("/api/rules")
@RequiredArgsConstructor
public class RuleController {

    private final DynamicRuleEngine dynamicRuleEngine;

    @GetMapping
    public ResponseEntity<List<RuleScript>> getAllRules() {
        return ResponseEntity.ok(dynamicRuleEngine.getAllRules());
    }

    @PostMapping
    public ResponseEntity<String> addRule(@RequestBody RuleScript ruleScript) {
        try {
            dynamicRuleEngine.addRule(ruleScript);
            return ResponseEntity.ok("规则添加成功");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("添加失败: " + e.getMessage());
        }
    }

    @PutMapping("/{ruleName}")
    public ResponseEntity<String> updateRule(@PathVariable String ruleName,
                                           @RequestBody RuleScript ruleScript) {
        try {
            dynamicRuleEngine.updateRule(ruleName, ruleScript);
            return ResponseEntity.ok("规则更新成功");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("更新失败: " + e.getMessage());
        }
    }

    @PostMapping("/execute/{ruleName}")
    public ResponseEntity<RuleExecuteResponse> executeRule(
            @PathVariable String ruleName,
            @RequestBody Map<String, Object> params) {
        RuleExecuteResponse response = dynamicRuleEngine.executeRule(ruleName, params);
        return ResponseEntity.ok(response);
    }
}

操作界面

为了方便大家快速直观的体验规则引擎,DEMO提供了一套简洁的操作界面,包含规则配置与业务场景模拟。

规则管理页面

主要功能:

  • 规则列表:展示所有规则及状态
  • 在线编辑:支持规则脚本的在线修改
  • 实时测试:规则修改后立即测试效果
  • 状态控制:一键启用/禁用规则

业务演示页面

主要功能:

  • 订单模拟器:输入订单信息,查看处理结果
  • 详细步骤:清晰展示每个规则的执行过程
  • 处理历史:记录历史处理结果

关键前端代码:

javascript 复制代码
// 动态执行规则
async function executeRule(ruleName, params) {
    try {
        const response = await fetch(`/api/rules/execute/${ruleName}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(params)
        });

        const result = await response.json();
        if (result.success) {
            displayResult(`执行成功!结果: ${result.result}`);
        } else {
            displayResult(`执行失败: ${result.errorMessage}`);
        }
    } catch (error) {
        displayResult(`请求失败: ${error.message}`);
    }
}

// 渲染处理步骤
function displayProcessSteps(processSteps) {
    const stepsHtml = processSteps.map((step, index) => {
        const beforeAmount = parseFloat(step.beforeAmount);
        const afterAmount = parseFloat(step.afterAmount);
        const reduction = parseFloat(step.reduction);

        return `
            <div class="process-step">
                <div class="step-number">${index + 1}</div>
                <div class="step-content">
                    <h4>${step.stepName}</h4>
                    <p>${step.description}</p>
                </div>
                <div class="step-result">
                    <div class="amount-change">¥${beforeAmount} → ¥${afterAmount}</div>
                    <div class="reduction">节省 ¥${reduction.toFixed(2)}</div>
                </div>
            </div>
        `;
    }).join('');

    document.getElementById('processingSteps').innerHTML = stepsHtml;
}

应用场景实战

场景1:电商平台动态定价

需求:根据用户等级和订单金额,动态计算优惠价格

传统方式

java 复制代码
// 硬编码,修改需要重新发布
if ("GOLD".equals(userLevel) && amount >= 100) {
    return amount * 0.8;
}

动态规则方式

javascript 复制代码
// 规则脚本,可随时修改
if (userLevel == "GOLD" && totalAmount >= 100) {
    return totalAmount * 0.8;
} else if (userLevel == "SILVER" && totalAmount >= 50) {
    return totalAmount * 0.9;
} else {
    return totalAmount;
}

效果对比

  • 响应速度:从小时级降低到分钟级
  • 发布风险:从系统级降低到规则级
  • 操作门槛:从开发人员扩展到业务人员

场景2:风控策略实时调整

需求:根据实时风险情况,动态调整审核策略

规则示例

javascript 复制代码
// 风控评分规则
score = baseScore;
if (userAge < 18) { score = score - 20; }
if (creditLevel == "HIGH") { score = score + 30; }
if (monthlyIncome > 10000) { score = score + 15; }
return score >= 60 ? "PASS" : "REJECT";

业务价值

  • 快速响应:市场风险变化时立即调整
  • 精细化控制:不同场景使用不同策略
  • A/B测试:同时运行多套策略进行对比

场景3:营销活动灵活配置

需求:支持复杂的营销活动规则

规则示例

javascript 复制代码
// 双11活动规则
if (activityType == "DOUBLE11") {
    if (totalAmount >= 1000) { return totalAmount - 200; }
    else if (totalAmount >= 500) { return totalAmount - 80; }
    else if (totalAmount >= 200) { return totalAmount - 30; }
}
return totalAmount;

运营效果

  • 活动预热:提前配置规则,定时生效
  • 实时调整:根据活动效果实时优化
  • 快速止损:发现问题立即回滚规则

总结

通过将业务规则从代码中剥离出来,我们实现了:

  1. 业务敏捷:规则变更从小时级提升到分钟级
  2. 系统稳定:减少了系统发布频次和风险
  3. 团队协作:业务人员可以直接参与规则配置
  4. 成本降低:减少了开发、测试、运维的工作量

github.com/yuboon/java...

相关推荐
bcbnb10 分钟前
Socket 抓包工具与实战,从抓取到定位(Socket 的命令、分析)
后端
用户83562907805112 分钟前
用Python轻松转换Excel表格为HTML格式
后端·python
用户0840598129020 分钟前
高版本的jdk在使用maven时,如何编译成低版本的class
后端
摇滚侠22 分钟前
Spring Boot 3零基础教程,整合Redis,笔记12
spring boot·redis·笔记
荣淘淘22 分钟前
互联网大厂Java求职面试全景实战解析(涵盖Spring Boot、微服务及云原生技术)
java·spring boot·redis·jwt·cloud native·microservices·interview
吃饭最爱32 分钟前
spring高级知识概览
spring boot
这里有鱼汤34 分钟前
炒股的尽头真的是玄学?我用八字+AI做了个实验,结果震惊
后端
hrrrrb39 分钟前
【Spring Security】认证(二)
java·后端·spring
程序员爱钓鱼1 小时前
Python编程实战 · 基础入门篇 | Python的版本与安装
后端·python