规则引擎实践
规则引擎将业务规则从代码中分离出来,让非技术人员也能动态管理业务逻辑。
1. 为什么需要规则引擎
| 传统方式 | 规则引擎方式 |
|---|---|
| 规则写死在代码里 | 规则可配置、可热更新 |
| 修改规则需要发版 | 运营人员实时调整 |
| 规则复杂时代码难以维护 | 规则外置,逻辑清晰 |
| 规则变化频繁时开发负担重 | 规则变化由业务方主导 |
典型场景:营销活动、风控策略、定价规则、审批流程
2. 规则引擎对比
| 引擎 | 特点 | 适用场景 | 学习成本 |
|---|---|---|---|
| Drools | 功能强大、语法完善、支持复杂规则链 | 复杂企业规则 | 高 |
| Aviator | 轻量、表达式求值、高性能 | 简单条件判断 | 低 |
| EasyRules | 简单注解驱动、易于集成 | 轻量规则 | 低 |
| QLExpress | 阿里开源、电商场景优化 | 交易规则 | 中 |
| SpEL | Spring内置、无需额外依赖、功能丰富 | Spring项目中的简单条件 | 低 |
3. 设计要点
3.1 规则模型
// 规则定义
public class Rule {
private String ruleId; // 规则ID
private String ruleName; // 规则名称
private String ruleGroup; // 规则分组
private Integer priority; // 优先级(数字越小越优先)
private String condition; // 条件表达式
private String action; // 执行动作
private Boolean enabled; // 是否启用
private LocalDateTime startTime; // 生效时间
private LocalDateTime endTime; // 失效时间
}
// 规则上下文(输入数据)
public class RuleContext {
private Map<String, Object> facts; // 事实数据
private Map<String, Object> result; // 规则执行结果
}
// 规则执行结果
public class RuleResult {
private boolean matched; // 是否匹配
private String ruleId; // 命中的规则ID
private Object actionResult; // 执行结果
private String message; // 说明信息
}
3.2 规则表设计
CREATE TABLE rule_definition (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
rule_id VARCHAR(64) UNIQUE NOT NULL,
rule_name VARCHAR(100) NOT NULL COMMENT '规则名称',
rule_group VARCHAR(50) COMMENT '规则分组',
priority INT DEFAULT 100 COMMENT '优先级',
condition TEXT NOT NULL COMMENT '条件表达式',
action TEXT COMMENT '执行动作',
enabled TINYINT DEFAULT 1 COMMENT '1-启用 0-禁用',
start_time DATETIME COMMENT '生效时间',
end_time DATETIME COMMENT '失效时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME,
INDEX idx_rule_group (rule_group),
INDEX idx_enabled (enabled)
) COMMENT '规则定义表';
CREATE TABLE rule_execute_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
rule_id VARCHAR(64),
input_data TEXT COMMENT '输入数据JSON',
output_data TEXT COMMENT '输出结果JSON',
execute_time INT COMMENT '执行耗时ms',
success TINYINT DEFAULT 1,
error_msg VARCHAR(500),
created_at DATETIME
) COMMENT '规则执行日志';
4. 实现方案
4.1 Aviator 轻量实现
@Service
public class AviatorRuleEngine {
// 缓存编译后的规则
private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();
public RuleResult execute(Rule rule, RuleContext context) {
try {
// 获取或编译表达式
Expression exp = expressionCache.computeIfAbsent(
rule.getRuleId(),
id -> AviatorEvaluator.compile(rule.getCondition())
);
// 合并全局变量和facts
Map<String, Object> env = new HashMap<>(context.getFacts());
// 执行规则
Boolean matched = (Boolean) exp.execute(env);
RuleResult result = new RuleResult();
result.setMatched(matched);
result.setRuleId(rule.getRuleId());
if (matched && rule.getAction() != null) {
// 执行动作(可以是另一个表达式或脚本)
Object actionResult = executeAction(rule.getAction(), context);
result.setActionResult(actionResult);
}
return result;
} catch (Exception e) {
return RuleResult.fail(rule.getRuleId(), e.getMessage());
}
}
}
4.2 Drools 复杂实现
@Configuration
public class DroolsConfig {
@Bean
public KieServices kieServices() {
return KieServices.Factory.get();
}
@Bean
public KieContainer kieContainer() {
// 从classpath加载规则
return kieServices().getKieClasspathContainer();
}
}
@Service
public class DroolsRuleEngine {
@Autowired
private KieContainer kieContainer;
public RuleResult execute(String ruleGroup, RuleContext context) {
KieSession kieSession = kieContainer.newKieSession(ruleGroup);
try {
// 插入事实
context.getFacts().forEach(kieSession::insert);
// 插入结果对象
RuleResult result = new RuleResult();
kieSession.setGlobal("result", result);
// 执行规则
kieSession.fireAllRules();
return result;
} finally {
kieSession.dispose();
}
}
}
4.3 SpEL(Spring Expression Language)实现
SpEL是Spring框架内置的表达式语言,无需额外依赖,特别适合Spring Boot项目。
基础SpEL规则引擎实现
@Service
public class SpelRuleEngine {
private final ExpressionParser parser = new SpelExpressionParser();
private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();
public RuleResult execute(Rule rule, RuleContext context) {
try {
// 检查规则是否在有效期内
if (!isRuleValid(rule)) {
return RuleResult.notValid(rule.getRuleId());
}
// 获取或编译表达式
Expression conditionExp = expressionCache.computeIfAbsent(
"condition_" + rule.getRuleId(),
id -> parser.parseExpression(rule.getCondition())
);
// 创建评估上下文
StandardEvaluationContext evalContext = new StandardEvaluationContext();
context.getFacts().forEach(evalContext::setVariable);
// 执行条件判断
Boolean matched = conditionExp.getValue(evalContext, Boolean.class);
RuleResult result = new RuleResult();
result.setMatched(matched != null && matched);
result.setRuleId(rule.getRuleId());
if (matched != null && matched && rule.getAction() != null) {
// 执行动作表达式
Expression actionExp = expressionCache.computeIfAbsent(
"action_" + rule.getRuleId(),
id -> parser.parseExpression(rule.getAction())
);
Object actionResult = actionExp.getValue(evalContext);
result.setActionResult(actionResult);
}
return result;
} catch (Exception e) {
return RuleResult.fail(rule.getRuleId(), e.getMessage());
}
}
private boolean isRuleValid(Rule rule) {
if (!Boolean.TRUE.equals(rule.getEnabled())) {
return false;
}
LocalDateTime now = LocalDateTime.now();
if (rule.getStartTime() != null && now.isBefore(rule.getStartTime())) {
return false;
}
if (rule.getEndTime() != null && now.isAfter(rule.getEndTime())) {
return false;
}
return true;
}
}
SpEL语法详解
1. 基础操作符
// 关系操作符
"orderAmount > 100"
"userLevel >= 2"
"status == 'ACTIVE'"
// 逻辑操作符
"orderAmount > 100 && userLevel >= 2"
"vipUser || orderAmount > 500"
"!(blacklisted)"
// 算术操作符
"orderAmount * 0.9"
"quantity + bonusQuantity"
"total / count"
2. 方法调用
// 字符串方法
"userName.length() > 5"
"email.contains('@')"
"status.toUpperCase() == 'ACTIVE'"
// 数学方法
"T(Math).max(orderAmount, minAmount)"
"T(Math).round(total * 100) / 100.0"
// 自定义方法(需要注册)
// 在EvaluationContext中注册方法
evalContext.registerFunction("calculateDiscount",
DiscountService.class.getDeclaredMethod("calculate", Double.class, Integer.class));
// 使用:"#calculateDiscount(orderAmount, userLevel)"
3. 集合操作
// 列表操作
"items.size() > 0"
"items.?[price > 100].size() > 0" // 过滤价格>100的商品
"items.![price].sum() > 500" // 计算所有商品价格总和
// Map操作
"userRoles['admin'] == true"
"permissions.containsKey('write')"
4. 变量和属性访问
// 直接访问变量(通过setVariable设置)
"#orderAmount > 100"
"#user.level >= 2"
// 对象属性访问
"#order.amount > 100"
"#user.profile.vipLevel >= 3"
5. 实际规则示例
// 营销规则
条件: "#orderAmount >= 100 && #userLevel >= 2"
动作: "#orderAmount * 0.9"
// 风控规则
条件: "#loginAttempts > 5 || #ipAddress in {'192.168.1.100', '10.0.0.50'}"
动作: "BLOCK"
// 定价规则
条件: "#product.category == 'electronics' && #user.vipLevel >= 3"
动作: "#basePrice * 0.85"
SpEL高级配置
@Configuration
public class SpelConfig {
@Bean
public SpelRuleEngine spelRuleEngine() {
SpelRuleEngine engine = new SpelRuleEngine();
// 配置自定义函数
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("now", System.class.getMethod("currentTimeMillis"));
context.registerFunction("formatDate",
DateTimeFormatter.class.getMethod("ofPattern", String.class));
// 配置类型转换器
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
typeConverter.registerCustomEditor(LocalDateTime.class, new LocalDateTimeEditor());
context.setTypeConverter(typeConverter);
return engine;
}
}
5. 规则管理后台
┌─────────────────────────────────────────┐
│ 规则管理后台 │
├─────────────────────────────────────────┤
│ 规则分组:□ 风控 □ 营销 □ 定价 │
│ │
│ 规则名称:满减规则 │
│ 条件:orderAmount >= 100 && userLevel >= 1 │
│ 动作:orderAmount * 0.9 │
│ 优先级:10 │
│ 状态:☑ 启用 ☐ 定时生效 │
│ │
│ [保存] [测试] [发布] │
└─────────────────────────────────────────┘
测试面板:
输入:{"orderAmount": 200, "userLevel": 2}
结果:✓ 匹配,执行动作 discount=180.0
6. 注意事项
- 规则冲突:同类型规则按优先级取最高,或全部执行后合并结果
- 规则循环:避免规则A触发B、B又触发A的死循环
- 性能问题:规则多时使用索引、分组加载,避免全量加载
- 规则回滚:规则变更前先备份,出问题可快速回滚
- 规则审计:记录规则变更历史,谁改了什么、什么时候改的
- SpEL安全:避免用户输入直接作为SpEL表达式,防止代码注入
7. 选型建议
- Spring Boot项目 + 简单规则:优先选择 SpEL(零依赖、易集成)
- 高性能要求 + 简单表达式:选择 Aviator(编译缓存、执行快)
- 复杂业务规则 + 企业级应用:选择 Drools(功能完整、生态成熟)
- 轻量级需求 + 快速开发:选择 EasyRules(注解驱动、简单直观)
最后更新:2026/05/13