首页
/ 如何在RuoYi-Cloud中构建动态规则引擎:从原理到实践

如何在RuoYi-Cloud中构建动态规则引擎:从原理到实践

2026-04-16 08:12:16作者:贡沫苏Truman

RuoYi-Cloud作为基于Spring Cloud & Alibaba的分布式微服务权限管理系统,面临业务规则频繁变更与系统耦合度高的挑战。本文将详解如何在RuoYi-Cloud中集成规则引擎,实现业务逻辑与代码解耦,提升系统灵活性与可维护性,解决传统硬编码规则带来的扩展性难题。

剖析规则引擎核心价值

在微服务架构中,业务规则的硬编码会导致系统扩展性差、变更成本高。规则引擎通过将业务规则从代码中剥离,以可配置的形式独立管理,实现"业务规则即数据"的架构理念。对于RuoYi-Cloud这类权限管理系统,规则引擎可显著提升权限策略、审批流程等业务规则的动态调整能力,降低服务间耦合度。

规则引擎与微服务集成架构示意图

选择适合的规则引擎方案

主流规则引擎技术对比

引擎类型 技术特性 性能表现 集成复杂度 推荐场景
Drools 完整规则生命周期管理,支持复杂规则 高(优化后) 企业级复杂业务规则
EasyRules 轻量级API,注解驱动 简单规则场景
QLExpress 高性能表达式执行,轻量级 高并发规则计算
MVEL 动态脚本执行,灵活度高 动态逻辑执行

RuoYi-Cloud适配建议

基于项目微服务架构特性,推荐采用Drools作为核心规则引擎,其优势在于:

  • 支持复杂规则网络与推理能力,满足权限管理系统的多层次规则需求
  • 完善的规则管理生命周期,支持动态加载与更新
  • 与Spring生态良好集成,符合RuoYi-Cloud技术栈

构建基础规则引擎框架

核心思路

通过在公共模块集成Drools,构建规则引擎基础服务,各业务模块通过统一接口调用规则引擎,实现规则执行与业务逻辑分离。

关键实现步骤

1. 添加依赖配置

ruoyi-common-core模块的pom.xml中添加Drools依赖:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>7.73.0.Final</version>
</dependency>
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>7.73.0.Final</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-spring</artifactId>
    <version>7.73.0.Final</version>
</dependency>

2. 配置规则引擎

创建DroolsConfig配置类,实现规则文件的加载与Kie容器管理:

@Configuration
public class DroolsConfig {
    
    private static final String RULES_BASE_PATH = "rules/";
    
    @Bean
    public KieContainer kieContainer() {
        KieServices kieServices = KieServices.Factory.get();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        
        // 加载classpath下所有规则文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            Resource[] resources = resolver.getResources("classpath*:" + RULES_BASE_PATH + "**/*.drl");
            for (Resource resource : resources) {
                String filePath = resource.getURI().toString();
                String rulePath = RULES_BASE_PATH + FilenameUtils.getName(filePath);
                kieFileSystem.write(ResourceFactory.newClassPathResource(rulePath, "UTF-8"));
            }
            
            KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
            Results results = kieBuilder.verify();
            if (results.hasMessages(Message.Level.ERROR)) {
                throw new RuntimeException("规则文件编译错误: " + results.getMessages());
            }
            
            KieModule kieModule = kieBuilder.getKieModule();
            return kieServices.newKieContainer(kieModule.getReleaseId());
        } catch (Exception e) {
            throw new RuntimeException("初始化规则引擎失败", e);
        }
    }
}

注意事项

  • 规则文件统一放置在src/main/resources/rules目录下
  • 确保规则文件编码格式为UTF-8,避免中文乱码
  • 添加编译验证,提前发现规则语法错误

实现动态规则执行服务

核心思路

封装规则引擎服务,提供统一的规则执行接口,支持不同业务场景的规则调用,并实现规则执行结果的标准化处理。

关键代码实现

1. 创建规则服务接口

public interface RuleService {
    /**
     * 执行规则并返回结果
     * @param ruleSessionName 规则会话名称
     * @param facts 事实对象列表
     * @return 规则执行结果
     */
    RuleExecutionResult executeRules(String ruleSessionName, List<Object> facts);
    
    /**
     * 执行规则并返回特定类型结果
     * @param ruleSessionName 规则会话名称
     * @param facts 事实对象列表
     * @param resultClass 结果类型
     * @return 类型化的规则执行结果
     */
    <T> T executeRulesWithResult(String ruleSessionName, List<Object> facts, Class<T> resultClass);
}

2. 实现规则服务

@Service
public class RuleServiceImpl implements RuleService {
    
    private final KieContainer kieContainer;
    
    @Autowired
    public RuleServiceImpl(KieContainer kieContainer) {
        this.kieContainer = kieContainer;
    }
    
    @Override
    public RuleExecutionResult executeRules(String ruleSessionName, List<Object> facts) {
        RuleExecutionResult result = new RuleExecutionResult();
        KieSession kieSession = null;
        
        try {
            kieSession = kieContainer.newKieSession(ruleSessionName);
            kieSession.setGlobal("ruleResult", result);
            
            // 插入事实对象
            if (facts != null && !facts.isEmpty()) {
                facts.forEach(kieSession::insert);
            }
            
            // 执行规则
            int firedRules = kieSession.fireAllRules();
            result.setFiredRulesCount(firedRules);
            result.setSuccess(true);
            
        } catch (Exception e) {
            result.setSuccess(false);
            result.setErrorMessage(e.getMessage());
            log.error("规则执行异常", e);
        } finally {
            if (kieSession != null) {
                kieSession.dispose();
            }
        }
        
        return result;
    }
    
    // 其他方法实现...
}

注意事项

  • 使用KieSession时确保及时释放资源,避免内存泄漏
  • 通过全局对象(Global)传递执行结果,便于规则文件中设置结果
  • 添加异常处理和日志记录,提高系统可维护性

典型业务场景实践

场景一:动态权限验证

需求描述

实现基于用户角色、部门、数据范围的动态权限验证,支持复杂的权限组合规则。

规则文件实现 (security-permission.drl)

package com.ruoyi.security.rules;

import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.domain.SysResource;
import com.ruoyi.common.core.domain.RuleExecutionResult;

global RuleExecutionResult ruleResult;

rule "AdminAllPermission"
    salience 100
    when
        $user : SysUser(roleIds contains "1") // 管理员角色ID
        $resource : SysResource()
    then
        ruleResult.addResult("permission", true);
        ruleResult.addMessage("管理员拥有全部权限");
        drools.halt(); // 终止后续规则执行
end

rule "DeptDataPermission"
    when
        $user : SysUser(deptId != null)
        $resource : SysResource(type == "DATA", dataScope == "DEPT")
    then
        boolean hasPermission = $user.getDeptId().equals($resource.getDeptId());
        ruleResult.addResult("permission", hasPermission);
        ruleResult.addMessage(hasPermission ? "拥有部门数据权限" : "无部门数据权限");
end

权限服务调用

@Service
public class PermissionServiceImpl implements IPermissionService {
    
    @Autowired
    private RuleService ruleService;
    
    @Override
    public boolean hasResourcePermission(SysUser user, SysResource resource) {
        List<Object> facts = new ArrayList<>();
        facts.add(user);
        facts.add(resource);
        
        RuleExecutionResult result = ruleService.executeRules("permissionSession", facts);
        return result.getResult("permission", Boolean.class, false);
    }
}

场景二:工作流审批规则

需求描述

实现基于金额、申请人职级、部门的多级审批流程自动路由。

规则文件实现 (workflow-approval.drl)

package com.ruoyi.workflow.rules;

import com.ruoyi.workflow.domain.ApprovalRequest;
import com.ruoyi.workflow.domain.ApprovalResult;

global ApprovalResult approvalResult;

rule "ManagerApproval"
    when
        $request : ApprovalRequest(amount <= 5000, 
                                 applicantLevel <= 3)
    then
        approvalResult.setApproverLevel(4); // 部门经理审批
        approvalResult.setApprovalSteps(1);
        approvalResult.setMessage("金额≤5000,部门经理审批");
end

rule "DirectorApproval"
    when
        $request : ApprovalRequest(amount > 5000 && amount <= 20000,
                                 applicantLevel <= 4)
    then
        approvalResult.setApproverLevel(5); // 总监审批
        approvalResult.setApprovalSteps(2);
        approvalResult.setMessage("5000<金额≤20000,总监审批");
end

rule "CEOApproval"
    when
        $request : ApprovalRequest(amount > 20000)
    then
        approvalResult.setApproverLevel(7); // CEO审批
        approvalResult.setApprovalSteps(3);
        approvalResult.setMessage("金额>20000,CEO审批");
end

场景三:用户注册验证规则

需求描述

实现用户注册信息的多维度验证,包括邮箱格式、密码强度、手机号验证等。

规则文件实现 (user-validation.drl)

package com.ruoyi.user.rules;

import com.ruoyi.system.domain.SysUser;
import com.ruoyi.common.core.domain.ValidationResult;

global ValidationResult validationResult;

rule "EmailFormatValidation"
    when
        $user : SysUser(email != null, 
                       email not matches "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
    then
        validationResult.addError("email", "邮箱格式不正确");
end

rule "PasswordStrengthValidation"
    when
        $user : SysUser(password != null,
                       password.length() < 8 ||
                       !(password matches ".*[A-Z].*") ||
                       !(password matches ".*[a-z].*") ||
                       !(password matches ".*[0-9].*"))
    then
        validationResult.addError("password", "密码必须包含大小写字母和数字,且长度至少8位");
end

优化规则引擎执行性能

规则会话池化管理

@Component
public class KieSessionPoolManager {
    
    private final KieContainer kieContainer;
    private final Map<String, BlockingQueue<KieSession>> sessionPools = new ConcurrentHashMap<>();
    
    @Autowired
    public KieSessionPoolManager(KieContainer kieContainer) {
        this.kieContainer = kieContainer;
        // 初始化常用规则会话池
        initPool("permissionSession", 10);
        initPool("approvalSession", 5);
    }
    
    private void initPool(String sessionName, int poolSize) {
        BlockingQueue<KieSession> pool = new ArrayBlockingQueue<>(poolSize);
        for (int i = 0; i < poolSize; i++) {
            pool.offer(createKieSession(sessionName));
        }
        sessionPools.put(sessionName, pool);
    }
    
    private KieSession createKieSession(String sessionName) {
        return kieContainer.newKieSession(sessionName);
    }
    
    public KieSession borrowSession(String sessionName) {
        try {
            BlockingQueue<KieSession> pool = sessionPools.get(sessionName);
            if (pool == null) {
                throw new IllegalArgumentException("未初始化的规则会话池: " + sessionName);
            }
            return pool.poll(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取规则会话超时", e);
        }
    }
    
    public void returnSession(String sessionName, KieSession session) {
        if (session != null) {
            session.reset(); // 重置会话状态
            BlockingQueue<KieSession> pool = sessionPools.get(sessionName);
            if (pool != null) {
                pool.offer(session);
            }
        }
    }
}

规则文件热加载

@Component
public class RuleFileWatcher {
    
    private final KieContainer kieContainer;
    private final String rulesPath;
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    
    @Autowired
    public RuleFileWatcher(KieContainer kieContainer, 
                          @Value("${ruoyi.rule.path:classpath:rules}") String rulesPath) {
        this.kieContainer = kieContainer;
        this.rulesPath = rulesPath;
        startWatcher();
    }
    
    private void startWatcher() {
        // 每30秒检查一次规则文件变更
        scheduler.scheduleAtFixedRate(this::checkRuleChanges, 0, 30, TimeUnit.SECONDS);
    }
    
    private void checkRuleChanges() {
        // 实现文件变更检测逻辑
        // 变更时调用 kieContainer.updateToVersion() 更新规则
    }
}

构建规则管理平台

规则管理数据库设计

CREATE TABLE `sys_rule` (
  `rule_id` bigint NOT NULL AUTO_INCREMENT COMMENT '规则ID',
  `rule_name` varchar(100) NOT NULL COMMENT '规则名称',
  `rule_code` varchar(50) NOT NULL COMMENT '规则编码',
  `rule_content` longtext COMMENT '规则内容',
  `rule_type` varchar(20) NOT NULL COMMENT '规则类型',
  `session_name` varchar(50) NOT NULL COMMENT '会话名称',
  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`rule_id`),
  UNIQUE KEY `uk_rule_code` (`rule_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规则定义表';

CREATE TABLE `sys_rule_version` (
  `version_id` bigint NOT NULL AUTO_INCREMENT COMMENT '版本ID',
  `rule_id` bigint NOT NULL COMMENT '规则ID',
  `version_num` int NOT NULL COMMENT '版本号',
  `rule_content` longtext COMMENT '规则内容',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`version_id`),
  KEY `idx_rule_id` (`rule_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规则版本表';

规则管理接口实现

@RestController
@RequestMapping("/system/rule")
public class SysRuleController extends BaseController {
    
    @Autowired
    private ISysRuleService ruleService;
    
    @GetMapping("/list")
    public TableDataInfo list(SysRule sysRule) {
        startPage();
        List<SysRule> list = ruleService.selectSysRuleList(sysRule);
        return getDataTable(list);
    }
    
    @GetMapping(value = "/{ruleId}")
    public AjaxResult getInfo(@PathVariable("ruleId") Long ruleId) {
        return AjaxResult.success(ruleService.selectSysRuleById(ruleId));
    }
    
    @PostMapping
    public AjaxResult add(@Validated @RequestBody SysRule sysRule) {
        if (StringUtils.isNull(sysRule.getRuleId())) {
            return toAjax(ruleService.insertSysRule(sysRule));
        }
        return toAjax(ruleService.updateSysRule(sysRule));
    }
    
    @DeleteMapping("/{ruleIds}")
    public AjaxResult remove(@PathVariable Long[] ruleIds) {
        return toAjax(ruleService.deleteSysRuleByIds(ruleIds));
    }
    
    @PostMapping("/deploy/{ruleId}")
    public AjaxResult deployRule(@PathVariable Long ruleId) {
        ruleService.deployRule(ruleId);
        return AjaxResult.success("规则部署成功");
    }
}

常见问题排查

规则不执行问题

  1. 检查规则文件路径:确保规则文件放置在src/main/resources/rules目录下
  2. 验证规则语法:使用Drools Workbench或在线DRL验证工具检查语法错误
  3. 检查KieSession名称:确保代码中使用的session名称与规则文件中定义的一致
  4. 查看规则条件:确认事实对象是否满足规则的when条件

性能问题优化

  1. 规则顺序优化:使用salience属性调整规则执行顺序,将高频规则前置
  2. 减少事实对象:只插入必要的事实对象,避免无关数据进入规则引擎
  3. 使用规则流:复杂规则场景下使用ruleflow-group控制规则执行流程
  4. 会话池调优:根据并发量调整会话池大小,避免频繁创建KieSession

规则冲突解决

  1. 明确规则优先级:使用salience属性设置规则优先级
  2. 使用activation-group:同一activation-group中的规则只能有一个被执行
  3. 添加终止条件:在关键规则中使用drools.halt()终止后续规则执行
  4. 规则拆分:将复杂规则拆分为多个独立规则,降低冲突概率

实战建议与未来扩展

实战建议

  1. 规则分层管理:按业务领域划分规则文件,如security/、workflow/、validation/等子目录
  2. 版本控制策略:所有规则变更必须通过版本管理,保留历史版本便于回滚
  3. 灰度发布:新规则先在测试环境验证,再通过规则管理平台动态部署到生产环境
  4. 监控告警:实现规则执行监控,对执行异常、执行超时等情况设置告警机制

未来扩展方向

  1. 规则可视化设计:开发图形化规则编辑器,支持拖拽式规则配置,降低业务人员使用门槛
  2. AI辅助规则生成:结合NLP技术,实现自然语言描述到规则代码的自动转换
  3. 规则推荐系统:基于历史规则执行数据,推荐最优规则组合和执行策略
  4. 分布式规则引擎:实现规则引擎的分布式部署,支持大规模规则并行执行

通过本文介绍的方案,我们在RuoYi-Cloud中构建了功能完善的动态规则引擎,实现了业务规则与代码的解耦,显著提升了系统的灵活性和可维护性。在实际项目中,建议根据业务复杂度合理设计规则粒度,建立完善的规则管理流程,充分发挥规则引擎在微服务架构中的价值。

登录后查看全文
热门项目推荐
相关项目推荐