基于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...

相关推荐
西门吹雪@1326 小时前
springboot项目添加请求链路追踪日志traceId
java·spring boot·后端
邂逅星河浪漫8 小时前
【Spring AI】Ollama大模型-智能对话实现+项目实战(Spring Boot + Vue)
java·人工智能·spring boot·vue·prompt·agent·ollama
moxiaoran57538 小时前
Springboot实现WebSocket通信(二)
spring boot·后端·websocket
Q_Q5110082858 小时前
python+springboot毕业季旅游一站式定制服务系统
java·spring boot·python·django·flask·node.js·旅游
杨杨杨大侠8 小时前
探索 Event 框架实战指南:微服务系统中的事件驱动通信:
java·spring boot·微服务·云原生·架构·系统架构
千里码aicood9 小时前
springboot+vue兼职招聘小程序(源码+文档+调试+基础修改+答疑)
vue.js·spring boot·小程序
会跑的葫芦怪10 小时前
Golang 赋值运算符与短声明 (= 与 :=)使用场景
开发语言·后端·golang
数据知道10 小时前
Go基础:Go语言函数和方法详解
开发语言·后端·golang·go语言
我是华为OD~HR~栗栗呀10 小时前
华为od-前端面经-22届非科班
java·前端·c++·后端·python·华为od·华为