
在业务开发中,你一定遇到过这些痛点:营销活动的折扣规则每周都要调整,每次改规则都要改代码、打包、上线,全量回归风险极高;风控系统的反欺诈规则每天都要迭代,硬编码的逻辑散落在各处,维护成本呈指数级增长;审批流程的条件规则频繁变动,业务人员只能排队等开发排期,业务响应效率极低。
规则引擎的核心价值,就是彻底解耦业务规则与系统代码,让频繁变动的业务规则无需开发介入,即可在线配置、动态生效,将规则迭代周期从“天级”压缩到“分钟级”。本文将从底层原理到生产级实战,用通俗语言讲透规则引擎的核心逻辑,帮你彻底掌握规则引擎的开发与落地。
规则引擎是一种嵌入在应用程序中的组件,它将业务决策逻辑从业务代码中剥离出来,使用预定义的语义模块编写业务规则,通过接收数据输入、解释业务规则、根据规则做出业务决策,实现业务逻辑的灵活配置与快速迭代。
通俗来讲:规则引擎就是把「如果满足XX条件,就执行XX动作」的业务逻辑,从Java代码中抽离出来,交给专门的引擎管理,无需修改代码、重启服务,即可完成规则的更新与生效。
规则引擎并非银弹,只有在规则频繁变动的场景下才能发挥最大价值,核心适用场景包括:
对比维度 | 硬编码实现 | 规则引擎实现 |
|---|---|---|
规则与代码耦合度 | 极高,规则写死在业务代码中 | 极低,规则完全与业务代码解耦 |
规则迭代效率 | 极低,改规则需改代码、编译、打包、上线 | 极高,规则可在线配置、动态生效 |
业务人员参与度 | 完全无法参与,必须依赖开发人员 | 可通过可视化界面直接配置规则 |
规则可维护性 | 极差,规则散落在代码各处,难以排查 | 极好,规则统一管理、版本控制、可追溯 |
上线风险 | 改规则需全量回归,上线风险高 | 规则灰度发布,增量更新,风险可控 |
适用场景 | 规则固定不变,几乎不迭代的场景 | 规则频繁变动,需快速响应业务变化的场景 |
很多开发者会把规则引擎与工作流引擎、流程编排引擎混为一谈,三者的核心边界完全不同:
本文所有选型均基于2026年2月最新的稳定版本,从性能、学习成本、功能完整性、社区活跃度四个维度进行权威对比,帮你快速选择适合业务场景的引擎。
引擎名称 | 最新稳定版 | 核心优势 | 核心劣势 | 适用场景 |
|---|---|---|---|---|
Drools | 8.44.0.Final | 业界标杆,功能最全,支持rete/phreak算法,社区活跃,文档完善,支持动态规则热更新 | 学习成本高,有一定性能开销,轻量场景过重 | 企业级复杂规则场景,大量规则的风控、合规系统 |
Easy Rules | 4.1.0 | 轻量级极简框架,API简单易懂,学习成本极低,零依赖,支持注解和编程式两种方式 | 功能简单,不支持复杂的模式匹配,无规则管理体系 | 简单规则场景,小型项目的规则校验、简单业务决策 |
LiteFlow | 2.11.1 | 国内开源,组件化规则编排,支持多种脚本语言,可视化界面完善,中文文档齐全,国内社区活跃 | 核心偏向流程编排,复杂规则模式匹配能力弱于Drools | 国内企业级项目,规则编排、流程驱动的业务场景 |
URule | 3.0.3 | 国产商业开源,全可视化规则配置,支持决策表、决策树,中文支持完善,适配国内企业需求 | 企业版收费,开源版功能有限,社区活跃度低于Drools | 国内政企项目,需要可视化规则配置、无代码开发的场景 |
选型建议:
规则引擎的核心架构分为5大核心模块,各模块职责清晰,协同完成规则的全生命周期管理,架构图如下:

Rete算法是由Charles Forgy在1979年提出的,是目前绝大多数规则引擎的核心算法,核心思想是用空间换时间,通过共享规则的条件节点,避免重复计算,大幅提升大量规则下的模式匹配效率。
举个通俗的例子:如果有1000条规则,每条规则都包含「订单金额>1000」这个条件,硬编码会对每条规则都执行一次这个判断,总共执行1000次;而Rete算法只会执行1次这个判断,把结果共享给所有用到这个条件的规则,极大减少重复计算。
Rete算法会将所有规则拆解成节点,构建一个有向无环图(Rete网络),核心节点与执行流程如下:

Drools 6.x之后引入了Phreak算法,针对Rete算法在事实更新时的性能瓶颈做了核心优化,也是目前Drools的默认算法,核心优化点:
规则引擎的执行分为5个核心阶段,形成完整的闭环:
本文所有实战代码均基于JDK 17编写。
Easy Rules是极简的轻量级规则引擎,零依赖,API简单易懂,适合简单规则场景,5分钟即可完成开发。
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
package com.jam.demo.rule;
import lombok.extern.slf4j.Slf4j;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 订单折扣规则
* @author ken
*/
@Slf4j
@Rule(name = "order_discount_rule", description = "订单金额折扣规则", priority = 1)
publicclass OrderDiscountRule {
/**
* 规则条件:订单金额大于等于1000,打9折
* @param orderAmount 订单金额
* @return 是否满足条件
*/
@Condition
public boolean isDiscountAvailable(@Fact("orderAmount") BigDecimal orderAmount) {
return orderAmount.compareTo(new BigDecimal("1000")) >= 0;
}
/**
* 规则执行动作:计算折扣后金额
* @param orderAmount 订单金额
*/
@Action
public void applyDiscount(@Fact("orderAmount") BigDecimal orderAmount) {
BigDecimal discountAmount = orderAmount.multiply(new BigDecimal("0.9")).setScale(2, RoundingMode.HALF_UP);
log.info("订单金额{}元,满足折扣条件,折扣后金额:{}元", orderAmount, discountAmount);
}
}
package com.jam.demo.test;
import com.jam.demo.rule.OrderDiscountRule;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import java.math.BigDecimal;
/**
* Easy Rules测试类
* @author ken
*/
publicclass EasyRuleTest {
public static void main(String[] args) {
// 1. 创建规则引擎
RulesEngine rulesEngine = new DefaultRulesEngine();
// 2. 创建规则集合
Rules rules = new Rules();
rules.register(new OrderDiscountRule());
// 3. 创建事实对象
Facts facts = new Facts();
facts.put("orderAmount", new BigDecimal("2000"));
// 4. 执行规则
rulesEngine.fire(rules, facts);
}
}
运行测试类,控制台输出如下,规则执行成功:
INFO com.jam.demo.rule.OrderDiscountRule - 订单金额2000元,满足折扣条件,折扣后金额:1800.00元
Drools是业界标杆的企业级规则引擎,本实战将从零搭建一个电商订单风控规则引擎,支持规则持久化、动态热更新、REST接口调用,可直接用于生产环境。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>rule-engine-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rule-engine-demo</name>
<description>Drools Rule Engine Demo</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<drools.version>8.44.0.Final</drools.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<springdoc.version>2.5.0</springdoc.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.1.0-jre</guava.version>
<lombok.version>1.18.32</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<version>${drools.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-internal</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
CREATE DATABASEIFNOTEXISTS rule_db DEFAULTCHARACTERSET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE rule_db;
DROPTABLEIFEXISTS t_rule_info;
CREATETABLE t_rule_info (
idBIGINTNOTNULL AUTO_INCREMENT COMMENT'主键ID',
rule_key VARCHAR(64) NOTNULLCOMMENT'规则唯一标识',
rule_name VARCHAR(128) NOTNULLCOMMENT'规则名称',
rule_content TEXTNOTNULLCOMMENT'规则内容(drl脚本)',
rule_type VARCHAR(32) NOTNULLCOMMENT'规则类型',
statusTINYINTNOTNULLDEFAULT1COMMENT'状态:0-禁用 1-启用',
versionINTNOTNULLDEFAULT1COMMENT'规则版本',
create_time DATETIME NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
update_time DATETIME NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
create_by VARCHAR(64) NOTNULLDEFAULT'system'COMMENT'创建人',
update_by VARCHAR(64) NOTNULLDEFAULT'system'COMMENT'更新人',
remark VARCHAR(512) DEFAULTNULLCOMMENT'备注',
PRIMARY KEY (id),
UNIQUEKEY uk_rule_key_version (rule_key, version),
KEY idx_status (status)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则信息表';
spring:
application:
name:rule-engine-demo
datasource:
url:jdbc:mysql://127.0.0.1:3306/rule_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username:root
password:root
driver-class-name:com.mysql.cj.jdbc.Driver
jackson:
date-format:yyyy-MM-ddHH:mm:ss
time-zone:Asia/Shanghai
mybatis-plus:
mapper-locations:classpath*:/mapper/**/*.xml
type-aliases-package:com.jam.demo.entity
configuration:
map-underscore-to-camel-case:true
cache-enabled:false
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
springdoc:
api-docs:
enabled:true
path:/v3/api-docs
swagger-ui:
enabled:true
path:/swagger-ui.html
tags-sorter:alpha
operations-sorter:alpha
server:
port:8080
规则信息实体类 RuleInfo
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 规则信息实体类
* @author ken
*/
@Data
@TableName("t_rule_info")
@Schema(description = "规则信息实体")
publicclass RuleInfo {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "规则唯一标识", example = "order_risk_rule")
private String ruleKey;
@Schema(description = "规则名称", example = "订单风控规则")
private String ruleName;
@Schema(description = "规则内容(drl脚本)", example = "rule \"xxx\" when then end")
private String ruleContent;
@Schema(description = "规则类型", example = "RISK")
private String ruleType;
@Schema(description = "状态:0-禁用 1-启用", example = "1")
private Integer status;
@Schema(description = "规则版本", example = "1")
private Integer version;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "创建人", example = "system")
private String createBy;
@Schema(description = "更新人", example = "system")
private String updateBy;
@Schema(description = "备注", example = "订单风控核心规则")
private String remark;
}
Mapper接口 RuleInfoMapper
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.RuleInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* 规则信息Mapper接口
* @author ken
*/
@Mapper
public interface RuleInfoMapper extends BaseMapper<RuleInfo> {
}
服务接口 RuleInfoService
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.RuleInfo;
import java.util.List;
/**
* 规则信息服务接口
* @author ken
*/
publicinterface RuleInfoService extends IService<RuleInfo> {
/**
* 查询所有启用的规则
* @return 启用的规则列表
*/
List<RuleInfo> listEnabledRules();
/**
* 根据规则key查询最新版本的启用规则
* @param ruleKey 规则唯一标识
* @return 规则信息
*/
RuleInfo getLatestEnabledRuleByKey(String ruleKey);
/**
* 新增或更新规则
* @param ruleInfo 规则信息
* @return 是否成功
*/
boolean saveOrUpdateRule(RuleInfo ruleInfo);
}
服务实现类 RuleInfoServiceImpl
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.mapper.RuleInfoMapper;
import com.jam.demo.service.RuleInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 规则信息服务实现类
* @author ken
*/
@Slf4j
@Service
publicclass RuleInfoServiceImpl extends ServiceImpl<RuleInfoMapper, RuleInfo> implements RuleInfoService {
privatefinal PlatformTransactionManager transactionManager;
public RuleInfoServiceImpl(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public List<RuleInfo> listEnabledRules() {
LambdaQueryWrapper<RuleInfo> queryWrapper = new LambdaQueryWrapper<RuleInfo>()
.eq(RuleInfo::getStatus, 1);
returnthis.list(queryWrapper);
}
@Override
public RuleInfo getLatestEnabledRuleByKey(String ruleKey) {
if (!StringUtils.hasText(ruleKey)) {
log.warn("规则key为空,无法查询规则");
returnnull;
}
LambdaQueryWrapper<RuleInfo> queryWrapper = new LambdaQueryWrapper<RuleInfo>()
.eq(RuleInfo::getRuleKey, ruleKey)
.eq(RuleInfo::getStatus, 1)
.orderByDesc(RuleInfo::getVersion)
.last("LIMIT 1");
returnthis.getOne(queryWrapper);
}
@Override
public boolean saveOrUpdateRule(RuleInfo ruleInfo) {
if (ObjectUtils.isEmpty(ruleInfo)) {
log.warn("规则信息为空,无法保存");
returnfalse;
}
if (!StringUtils.hasText(ruleInfo.getRuleKey())) {
log.warn("规则key为空,无法保存");
returnfalse;
}
// 编程式事务管理
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("saveOrUpdateRuleTransaction");
def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 查询当前规则的最新版本号
RuleInfo latestRule = getLatestEnabledRuleByKey(ruleInfo.getRuleKey());
if (!ObjectUtils.isEmpty(latestRule)) {
// 版本号+1,禁用旧版本规则
ruleInfo.setVersion(latestRule.getVersion() + 1);
latestRule.setStatus(0);
this.updateById(latestRule);
} else {
ruleInfo.setVersion(1);
}
// 保存新版本规则
boolean result = this.save(ruleInfo);
transactionManager.commit(status);
log.info("规则{}保存成功,版本号:{}", ruleInfo.getRuleKey(), ruleInfo.getVersion());
return result;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("规则保存失败,回滚事务", e);
returnfalse;
}
}
}
package com.jam.demo.config;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.service.RuleInfoService;
import lombok.extern.slf4j.Slf4j;
import org.drools.compiler.compiler.DroolsParserException;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.compiler.kie.builder.impl.KieFileSystemImpl;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.List;
/**
* Drools规则引擎配置类
* @author ken
*/
@Slf4j
@Configuration
publicclass DroolsConfig {
privatefinal RuleInfoService ruleInfoService;
public DroolsConfig(RuleInfoService ruleInfoService) {
this.ruleInfoService = ruleInfoService;
}
/**
* 初始化KieServices单例
* @return KieServices实例
*/
@Bean
public KieServices kieServices() {
return KieServices.Factory.get();
}
/**
* 初始化KieFileSystem,加载数据库中的规则
* @param kieServices KieServices实例
* @return KieFileSystem实例
* @throws DroolsParserException 规则解析异常
* @throws IOException IO异常
*/
@Bean
public KieFileSystem kieFileSystem(KieServices kieServices) throws DroolsParserException, IOException {
KieFileSystem kieFileSystem = new KieFileSystemImpl();
// 加载所有启用的规则
List<RuleInfo> ruleList = ruleInfoService.listEnabledRules();
if (CollectionUtils.isEmpty(ruleList)) {
log.warn("未查询到启用的规则,初始化空的KieFileSystem");
return kieFileSystem;
}
// 遍历规则,写入KieFileSystem
for (RuleInfo ruleInfo : ruleList) {
String ruleKey = ruleInfo.getRuleKey();
String ruleContent = ruleInfo.getRuleContent();
if (!StringUtils.hasText(ruleContent)) {
log.warn("规则{}内容为空,跳过加载", ruleKey);
continue;
}
String path = String.format("rules/%s_%d.drl", ruleKey, ruleInfo.getVersion());
kieFileSystem.write(path, ruleContent);
log.info("规则{}加载成功,路径:{}", ruleKey, path);
}
return kieFileSystem;
}
/**
* 初始化KieBuilder,构建规则模块
* @param kieServices KieServices实例
* @param kieFileSystem KieFileSystem实例
* @return KieBuilder实例
*/
@Bean
public KieBuilder kieBuilder(KieServices kieServices, KieFileSystem kieFileSystem) {
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
// 检查规则编译结果
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
List<Message> errorMessages = results.getMessages(Message.Level.ERROR);
log.error("规则编译失败,错误信息:{}", errorMessages);
thrownew RuntimeException("规则编译失败:" + errorMessages);
}
log.info("规则编译成功,无错误");
return kieBuilder;
}
/**
* 初始化KieModule,规则模块
* @param kieBuilder KieBuilder实例
* @return KieModule实例
*/
@Bean
public KieModule kieModule(KieBuilder kieBuilder) {
return kieBuilder.getKieModule();
}
/**
* 初始化KieContainer,规则容器
* @param kieServices KieServices实例
* @param kieModule KieModule实例
* @return KieContainer实例
*/
@Bean
public KieContainer kieContainer(KieServices kieServices, KieModule kieModule) {
return kieServices.newKieContainer(kieModule.getReleaseId());
}
/**
* 初始化KieSession,规则会话,用于执行规则
* @param kieContainer KieContainer实例
* @return KieSession实例
*/
@Bean
public KieSession kieSession(KieContainer kieContainer) {
KieSession kieSession = kieContainer.newKieSession();
log.info("KieSession初始化成功");
return kieSession;
}
/**
* 动态刷新规则,无需重启服务
* @param kieServices KieServices实例
* @param kieContainer KieContainer实例
*/
public void refreshRules(KieServices kieServices, KieContainer kieContainer) {
try {
KieFileSystem kieFileSystem = kieFileSystem(kieServices);
KieBuilder kieBuilder = kieBuilder(kieServices, kieFileSystem);
KieModule kieModule = kieModule(kieBuilder);
((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieModule);
log.info("规则动态刷新成功");
} catch (Exception e) {
log.error("规则动态刷新失败", e);
thrownew RuntimeException("规则动态刷新失败", e);
}
}
}
风控事实对象 OrderRiskFact
package com.jam.demo.fact;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单风控事实对象,用于规则引擎执行
* @author ken
*/
@Data
@Schema(description = "订单风控事实对象")
publicclass OrderRiskFact {
@Schema(description = "订单号", example = "ORD202602280001")
private String orderNo;
@Schema(description = "用户ID", example = "10001")
private Long userId;
@Schema(description = "用户等级:1-普通 2-VIP 3-超级VIP", example = "1")
private Integer userLevel;
@Schema(description = "订单金额", example = "5000.00")
private BigDecimal orderAmount;
@Schema(description = "收货地址是否为常用地址", example = "true")
private Boolean isCommonAddress;
@Schema(description = "用户近30天订单数", example = "5")
private Integer orderCount30Days;
@Schema(description = "用户历史拒付次数", example = "0")
private Integer refusePayCount;
@Schema(description = "风控结果:PASS-放行 REVIEW-人工审核 REJECT-拦截", example = "PASS")
private String riskResult;
@Schema(description = "风控描述", example = "订单正常,放行")
private String riskDesc;
@Schema(description = "订单创建时间")
private LocalDateTime orderCreateTime;
}
风控规则文件 order_risk_rule.drl(resources/rules目录下)
package com.jam.demo.rules;
dialect "mvel"
import com.jam.demo.fact.OrderRiskFact
global org.slf4j.Logger log;
/**
* 规则1:超级VIP用户,订单金额小于10000,直接放行
*/
rule "super_vip_pass_rule"
salience 100
when
$fact: OrderRiskFact(userLevel == 3, orderAmount < 10000, refusePayCount == 0)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("超级VIP用户,订单正常,直接放行");
log.info("订单{}触发超级VIP放行规则", $fact.getOrderNo());
end
/**
* 规则2:VIP用户,订单金额小于5000,常用地址,直接放行
*/
rule "vip_pass_rule"
salience 90
when
$fact: OrderRiskFact(userLevel == 2, orderAmount < 5000, isCommonAddress == true, refusePayCount == 0)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("VIP用户,订单正常,直接放行");
log.info("订单{}触发VIP放行规则", $fact.getOrderNo());
end
/**
* 规则3:普通用户,订单金额大于10000,直接拦截
*/
rule "normal_user_reject_rule"
salience 80
when
$fact: OrderRiskFact(userLevel == 1, orderAmount >= 10000)
then
$fact.setRiskResult("REJECT");
$fact.setRiskDesc("普通用户订单金额过高,系统自动拦截");
log.warn("订单{}触发高金额拦截规则", $fact.getOrderNo());
end
/**
* 规则4:用户有历史拒付记录,直接拦截
*/
rule "refuse_pay_reject_rule"
salience 1000
when
$fact: OrderRiskFact(refusePayCount > 0)
then
$fact.setRiskResult("REJECT");
$fact.setRiskDesc("用户有历史拒付记录,系统自动拦截");
log.error("订单{}触发拒付记录拦截规则", $fact.getOrderNo());
end
/**
* 规则5:非常用地址,近30天无订单,订单金额大于2000,人工审核
*/
rule "uncommon_address_review_rule"
salience 70
when
$fact: OrderRiskFact(isCommonAddress == false, orderCount30Days == 0, orderAmount >= 2000)
then
$fact.setRiskResult("REVIEW");
$fact.setRiskDesc("非常用地址且无近期订单,需人工审核");
log.info("订单{}触发人工审核规则", $fact.getOrderNo());
end
/**
* 规则6:默认规则,无匹配规则时放行
*/
rule "default_pass_rule"
salience 0
when
$fact: OrderRiskFact(riskResult == null)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("无匹配风控规则,默认放行");
log.info("订单{}触发默认放行规则", $fact.getOrderNo());
end
风控服务接口与实现类
package com.jam.demo.service;
import com.jam.demo.fact.OrderRiskFact;
/**
* 订单风控服务接口
* @author ken
*/
public interface OrderRiskService {
/**
* 执行订单风控规则校验
* @param fact 订单风控事实对象
* @return 风控结果
*/
OrderRiskFact executeRiskCheck(OrderRiskFact fact);
}
package com.jam.demo.service.impl;
import com.jam.demo.config.DroolsConfig;
import com.jam.demo.fact.OrderRiskFact;
import com.jam.demo.service.OrderRiskService;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
/**
* 订单风控服务实现类
* @author ken
*/
@Slf4j
@Service
publicclass OrderRiskServiceImpl implements OrderRiskService {
privatefinal KieSession kieSession;
privatefinal KieServices kieServices;
privatefinal KieContainer kieContainer;
privatefinal DroolsConfig droolsConfig;
public OrderRiskServiceImpl(KieSession kieSession, KieServices kieServices, KieContainer kieContainer, DroolsConfig droolsConfig) {
this.kieSession = kieSession;
this.kieServices = kieServices;
this.kieContainer = kieContainer;
this.droolsConfig = droolsConfig;
}
@Override
public OrderRiskFact executeRiskCheck(OrderRiskFact fact) {
if (ObjectUtils.isEmpty(fact)) {
log.warn("风控事实对象为空,无法执行规则");
returnnull;
}
try {
// 设置全局日志对象
kieSession.setGlobal("log", log);
// 插入事实对象
kieSession.insert(fact);
// 执行所有规则
int ruleFiredCount = kieSession.fireAllRules();
log.info("订单{}风控规则执行完成,触发规则数:{}", fact.getOrderNo(), ruleFiredCount);
return fact;
} catch (Exception e) {
log.error("订单{}风控规则执行失败", fact.getOrderNo(), e);
thrownew RuntimeException("风控规则执行失败", e);
} finally {
// 清空会话,避免事实对象残留
kieSession.dispose();
}
}
/**
* 刷新规则
*/
public void refreshRules() {
droolsConfig.refreshRules(kieServices, kieContainer);
}
}
订单风控控制器
package com.jam.demo.controller;
import com.jam.demo.fact.OrderRiskFact;
import com.jam.demo.service.OrderRiskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.ObjectUtils;
/**
* 订单风控控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/risk")
@Tag(name = "订单风控接口", description = "订单风控规则执行相关接口")
publicclass OrderRiskController {
privatefinal OrderRiskService orderRiskService;
public OrderRiskController(OrderRiskService orderRiskService) {
this.orderRiskService = orderRiskService;
}
@PostMapping("/check")
@Operation(summary = "执行订单风控校验", description = "传入订单信息,执行风控规则,返回风控结果")
public ResponseEntity<OrderRiskFact> executeRiskCheck(@RequestBody OrderRiskFact fact) {
if (ObjectUtils.isEmpty(fact)) {
return ResponseEntity.badRequest().body(null);
}
OrderRiskFact result = orderRiskService.executeRiskCheck(fact);
return ResponseEntity.ok(result);
}
}
规则管理控制器
package com.jam.demo.controller;
import com.jam.demo.config.DroolsConfig;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.service.RuleInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 规则管理控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/rule")
@Tag(name = "规则管理接口", description = "规则信息管理、动态刷新相关接口")
publicclass RuleManageController {
privatefinal RuleInfoService ruleInfoService;
privatefinal DroolsConfig droolsConfig;
privatefinal KieServices kieServices;
privatefinal KieContainer kieContainer;
public RuleManageController(RuleInfoService ruleInfoService, DroolsConfig droolsConfig, KieServices kieServices, KieContainer kieContainer) {
this.ruleInfoService = ruleInfoService;
this.droolsConfig = droolsConfig;
this.kieServices = kieServices;
this.kieContainer = kieContainer;
}
@GetMapping("/list/enabled")
@Operation(summary = "查询所有启用的规则", description = "查询所有状态为启用的规则列表")
public ResponseEntity<List<RuleInfo>> listEnabledRules() {
List<RuleInfo> ruleList = ruleInfoService.listEnabledRules();
return ResponseEntity.ok(ruleList);
}
@GetMapping("/get/{ruleKey}")
@Operation(summary = "查询最新版本规则", description = "根据规则key查询最新版本的启用规则")
public ResponseEntity<RuleInfo> getLatestRule(@PathVariable String ruleKey) {
if (!StringUtils.hasText(ruleKey)) {
return ResponseEntity.badRequest().body(null);
}
RuleInfo ruleInfo = ruleInfoService.getLatestEnabledRuleByKey(ruleKey);
return ResponseEntity.ok(ruleInfo);
}
@PostMapping("/save")
@Operation(summary = "新增或更新规则", description = "新增规则或更新规则版本,自动禁用旧版本")
public ResponseEntity<Boolean> saveOrUpdateRule(@RequestBody RuleInfo ruleInfo) {
if (ObjectUtils.isEmpty(ruleInfo)) {
return ResponseEntity.badRequest().body(false);
}
boolean result = ruleInfoService.saveOrUpdateRule(ruleInfo);
return ResponseEntity.ok(result);
}
@PostMapping("/refresh")
@Operation(summary = "动态刷新规则", description = "重新加载数据库中的规则,无需重启服务")
public ResponseEntity<Boolean> refreshRules() {
try {
droolsConfig.refreshRules(kieServices, kieContainer);
return ResponseEntity.ok(true);
} catch (Exception e) {
log.error("规则刷新失败", e);
return ResponseEntity.internalServerError().body(false);
}
}
}
package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 规则引擎demo启动类
* @author ken
*/
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
publicclass RuleEngineDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RuleEngineDemoApplication.class, args);
}
}
/api/risk/check接口,传入以下测试参数,即可执行风控规则:{
"orderNo": "ORD202602280001",
"userId": 10001,
"userLevel": 1,
"orderAmount": 20000,
"isCommonAddress": true,
"orderCount30Days": 5,
"refusePayCount": 0,
"orderCreateTime": "2026-02-28T12:00:00"
}
{
"orderNo": "ORD202602280001",
"userId": 10001,
"userLevel": 1,
"orderAmount": 20000,
"isCommonAddress": true,
"orderCount30Days": 5,
"refusePayCount": 0,
"riskResult": "REJECT",
"riskDesc": "普通用户订单金额过高,系统自动拦截",
"orderCreateTime": "2026-02-28T12:00:00"
}
dispose()方法销毁会话,避免事实对象残留导致内存泄漏常见坑 | 根因分析 | 解决方案 |
|---|---|---|
规则死循环 | 规则执行中更新事实对象,导致规则重新触发,无限循环 | 给规则添加no-loop或lock-on-active属性,避免同一规则重复触发 |
规则冲突 | 多个规则同时满足条件,优先级设置不合理,执行结果不符合预期 | 明确设置规则的salience优先级,核心规则优先级更高,避免同优先级规则 |
内存泄漏 | KieSession未销毁,事实对象无法回收,内存持续上涨 | 使用try-finally结构,每次执行后必须调用dispose()方法销毁会话 |
动态规则加载线程安全问题 | 多线程同时刷新规则,导致规则执行异常 | 规则刷新添加分布式锁,保证刷新操作的原子性,刷新时暂停新的规则执行 |
规则语法错误导致容器崩溃 | 动态更新的规则存在语法错误,导致整个KieContainer初始化失败 | 规则保存前先做语法校验,隔离错误规则,只加载编译通过的规则 |
该用的场景:规则频繁变动,需要快速响应业务变化;业务人员需要直接配置规则,无需开发介入;规则数量多,逻辑复杂,硬编码难以维护;需要统一管理规则,支持版本控制和审计。不该用的场景:规则固定不变,几乎不迭代;规则逻辑极其简单,只有1-2个条件;性能要求极高(纳秒级响应),规则引擎有固定的性能开销;团队无规则引擎使用经验,学习成本过高。
规则引擎的核心价值,不是替代开发人员写代码,而是将业务规则的控制权还给业务方,让技术团队专注于核心系统的架构设计,而不是陷入无休止的规则迭代中。本文从底层原理到生产级实战,完整覆盖了规则引擎的核心知识,所有代码均可直接编译运行,帮你快速掌握规则引擎的开发与落地。
规则引擎不是银弹,只有在合适的场景下才能发挥最大价值。在实际开发中,需要根据业务场景的复杂度、规则迭代频率、团队技术栈,选择合适的规则引擎,设计合理的规则架构,才能真正实现业务与技术的解耦,提升业务响应效率。