Explorar el Código

新增drools数据库读取规则demo

车车 hace 2 semanas
padre
commit
74726201a9

+ 9 - 0
yudao-module-iscs/pom.xml

@@ -66,26 +66,35 @@
         </dependency>
 
         <!-- Drools 核心依赖 -->
+        <!--drools-core:这是Drools规则引擎的核心包,包含了Drools规则引擎的基本功能,如规则存储、规则执行等核心机制-->
         <dependency>
             <groupId>org.drools</groupId>
             <artifactId>drools-core</artifactId>
             <version>8.44.0.Final</version>
         </dependency>
+        <!--drools-compiler:这个包提供了对Drools规则文件(如DRL文件)的编译功能,将规则文件编译成可以在运行时执行的规则-->
         <dependency>
             <groupId>org.drools</groupId>
             <artifactId>drools-compiler</artifactId>
             <version>8.44.0.Final</version>
         </dependency>
+        <!--drools-decisiontables:支持从Excel或CSV文件中加载规则,这些文件通常被称为决策表,它们提供了一种直观的方式来定义复杂的业务规则-->
         <dependency>
             <groupId>org.drools</groupId>
             <artifactId>drools-decisiontables</artifactId>
             <version>8.44.0.Final</version>
         </dependency>
+        <!--drools-mvel:Drools所需的脚本语言包,MVEL(MVFLEX Expression Language)是一个动态的表达式语言,用于在Drools规则中编写更复杂的逻辑。-->
         <dependency>
             <groupId>org.drools</groupId>
             <artifactId>drools-mvel</artifactId>
             <version>8.44.0.Final</version>
         </dependency>
+        <dependency>
+            <groupId>org.kie</groupId>
+            <artifactId>kie-api</artifactId>
+            <version>8.44.0.Final</version>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>

+ 49 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/config/DroolsConfig.java

@@ -28,4 +28,53 @@ public class DroolsConfig {
         ReleaseId releaseId = kieModule.getReleaseId(); // 获取模块的唯一标识符
         return kieServices.newKieContainer(releaseId);   // 根据 ReleaseId 创建容器
     }
+
+    // 以下是sql加载demo
+    /*@Bean
+    public KieContainer kieContainer2() {
+        String ruleContent = "package rules;\n" +
+                "import cn.iocoder.yudao.module.iscs.dal.dataobject.Person;\n" +
+                "\n" +
+                "dialect \"mvel\" // 默认就是MVEL表达式语言\n" +
+                "\n" +
+                "rule \"checkHighSalary\"\n" +
+                "when\n" +
+                "    $p : Person(age > 30 && salary >= 10000)\n" +
+                "then\n" +
+                "    System.out.println(\"警告:[\" + $p.getName() + \"]薪资过高sqldrl!\");\n" +
+                "end\n" +
+                "\n" +
+                "rule \"checkHighSalary2\"\n" +
+                "when\n" +
+                "    $p : Person(age > 20 && salary >= 8000)\n" +
+                "then\n" +
+                "    System.out.println(\"警告:[\" + $p.getName() + \"]薪资过高2sqldrl!\");\n" +
+                "end\n";
+        // 校验规则内容不为空
+        if (ruleContent == null || ruleContent.trim().isEmpty()) {
+            throw new IllegalArgumentException("规则内容ruleContent不能为空");
+        }
+
+        KieServices kieServices = KieServices.Factory.get();
+        KieFileSystem kfs = kieServices.newKieFileSystem();
+
+        // 关键:将ruleContent字符串转换为Resource,写入KieFileSystem
+        // 注意:需要指定资源路径(模拟文件结构,Drools会根据路径识别规则)
+        String rulePath = "src/main/resources/rules/dynamic-rule.drl"; // 自定义路径,保持规则目录结构
+        kfs.write(
+                rulePath,
+                ResourceFactory.newByteArrayResource(ruleContent.getBytes(StandardCharsets.UTF_8)) // 字符串转资源
+        );
+
+        // 构建规则并校验编译结果
+        KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
+        Results results = kieBuilder.getResults();
+        if (results.hasMessages(Message.Level.ERROR)) {
+            throw new RuntimeException("规则编译失败:" + results.getMessages());
+        }
+
+        // 创建并返回KieContainer
+        KieModule kieModule = kieBuilder.getKieModule();
+        return kieServices.newKieContainer(kieModule.getReleaseId());
+    }*/
 }

+ 0 - 36
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/RulesController.java

@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.iscs.controller.admin;
-
-import cn.iocoder.yudao.module.iscs.dal.dataobject.Person;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.kie.api.runtime.KieContainer;
-import org.kie.api.runtime.KieSession;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-@Tag(name = "管理后台 - uize------------")
-@RequestMapping("/rules")
-public class RulesController {
-
-    @Autowired
-    private KieContainer kieContainer;
-
-    @Operation(summary = "规则--------")
-    @PostMapping("/execute")
-    public Person executeRule(@RequestBody Person person) {
-        KieSession session = null; // 声明在外部以便在 finally 块中访问
-        try {
-            session = kieContainer.newKieSession();
-            session.insert(person);      // 插入事实对象到工作内存
-            session.fireAllRules();      // 触发所有匹配的规则
-            return person;               // 返回可能已被修改的对象
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to execute rules", e);
-        } finally {
-            if (session != null) {
-                session.dispose(); // &#9989; 显式释放资源
-            }
-        }
-    }
-}

+ 171 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/rule/DynamicRuleManager.java

@@ -0,0 +1,171 @@
+package cn.iocoder.yudao.module.iscs.controller.admin.rule;
+
+import cn.iocoder.yudao.module.iscs.dal.dataobject.rule.RuleDefinitionsDO;
+import cn.iocoder.yudao.module.iscs.service.rule.RuleDefinitionsService;
+import org.kie.api.KieServices;
+import org.kie.api.builder.KieBuilder;
+import org.kie.api.builder.KieFileSystem;
+import org.kie.api.builder.KieModule;
+import org.kie.api.builder.Message.Level;
+import org.kie.api.builder.Results;
+import org.kie.api.runtime.KieContainer;
+import org.kie.internal.io.ResourceFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Component
+public class DynamicRuleManager {
+
+    @Autowired
+    private RuleDefinitionsService ruleDefinitionsService;
+
+    // 原子引用存储当前KieContainer(线程安全)
+    private final AtomicReference<KieContainer> currentKieContainer = new AtomicReference<>();
+
+    // 记录每条规则的当前版本(key:规则唯一标识,如名称或ID;value:版本号)
+    private Map<String, Integer> ruleVersionMap = new HashMap<>();
+
+    public DynamicRuleManager(RuleDefinitionsService ruleDefinitionsService) {
+        this.ruleDefinitionsService = ruleDefinitionsService;
+        // 初始化时加载所有规则
+        loadRuleFromDb();
+    }
+
+    /**
+     * 从数据库加载所有规则并更新KieContainer
+     */
+    public void loadRuleFromDb() {
+        System.out.println("开始加载所有drools规则");
+        try {
+            // 1. 查询所有需要加载的规则(从Service获取多条规则)
+            List<RuleDefinitionsDO> allRules = ruleDefinitionsService.getAllValidRules();
+            if (allRules.isEmpty()) {
+                throw new RuntimeException("数据库中未找到任何有效规则");
+            }
+
+            // 2. 检查是否有规则需要更新(版本变化、新增或删除)
+            if (!isNeedUpdate(allRules)) {
+                System.out.println("所有规则版本未更新,无需重新加载");
+                return;
+            }
+
+            // 3. 构建KieFileSystem,批量写入所有规则
+            KieServices kieServices = KieServices.Factory.get();
+            KieFileSystem kfs = kieServices.newKieFileSystem();
+
+            for (RuleDefinitionsDO rule : allRules) {
+                String ruleName = rule.getName(); // 规则唯一标识(假设名称唯一)
+                String drlContent = rule.getContent(); // 规则内容
+
+                // 校验规则内容
+                if (drlContent == null || drlContent.trim().isEmpty()) {
+                    throw new RuntimeException("规则[" + ruleName + "]内容为空");
+                }
+
+                // 清洗规则内容(去除可能的多余引号、转义符,避免编译错误)
+                String cleanedContent = cleanDrlContent(drlContent);
+
+                // 生成唯一虚拟路径(用规则ID+名称确保不重复)
+                String virtualPath = "src/main/resources/rules/dynamic-" + rule.getId() + "-" + ruleName + ".drl";
+                kfs.write(virtualPath, ResourceFactory.newByteArrayResource(cleanedContent.getBytes(StandardCharsets.UTF_8)));
+                System.out.println("已加载规则:" + ruleName + "(版本:" + rule.getVersion() + ")");
+            }
+
+            // 4. 编译所有规则
+            KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
+            Results results = kieBuilder.getResults();
+            if (results.hasMessages(Level.ERROR)) {
+                throw new RuntimeException("规则编译失败:" + results.getMessages());
+            }
+
+            // 5. 创建新的KieContainer并更新版本记录
+            KieModule kieModule = kieBuilder.getKieModule();
+            KieContainer newKieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
+
+            // 原子更新容器(线程安全)
+            currentKieContainer.set(newKieContainer);
+            // 更新版本Map(记录当前所有规则的最新版本)
+            updateRuleVersionMap(allRules);
+
+            System.out.println("所有规则更新成功,共加载 " + allRules.size() + " 条规则");
+
+        } catch (Exception e) {
+            System.err.println("规则加载失败,使用旧规则:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 检查是否需要更新规则(任一规则版本变化、新增或删除)
+     */
+    private boolean isNeedUpdate(List<RuleDefinitionsDO> newRules) {
+        // 首次加载(版本Map为空),需要更新
+        if (ruleVersionMap.isEmpty()) {
+            return true;
+        }
+
+        // 检查新规则中是否有版本变化或新增规则
+        for (RuleDefinitionsDO rule : newRules) {
+            String ruleName = rule.getName();
+            Integer newVersion = rule.getVersion();
+            Integer currentVersion = ruleVersionMap.get(ruleName);
+
+            // 规则新增(当前版本Map中无此规则)或版本提升,需要更新
+            if (currentVersion == null || !currentVersion.equals(newVersion)) {
+                return true;
+            }
+        }
+
+        // 检查是否有规则被删除(当前版本Map中有,但新规则中无)
+        for (String existingRuleName : ruleVersionMap.keySet()) {
+            boolean existsInNew = newRules.stream()
+                    .anyMatch(rule -> rule.getName().equals(existingRuleName));
+            if (!existsInNew) {
+                return true; // 有规则被删除,需要更新
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 更新规则版本Map(存储当前所有规则的最新版本)
+     */
+    private void updateRuleVersionMap(List<RuleDefinitionsDO> newRules) {
+        Map<String, Integer> newVersionMap = new HashMap<>();
+        for (RuleDefinitionsDO rule : newRules) {
+            newVersionMap.put(rule.getName(), rule.getVersion());
+        }
+        ruleVersionMap = newVersionMap;
+    }
+
+    /**
+     * 清洗DRL内容(去除多余引号、转义符,避免语法错误)
+     */
+    private String cleanDrlContent(String drlContent) {
+        if (drlContent == null) {
+            return null;
+        }
+        return drlContent
+                .trim()
+                .replaceAll("^\"", "") // 去除开头的双引号
+                .replaceAll("\"$", "") // 去除结尾的双引号
+                .replaceAll("\\\\n", "\n"); // 替换转义换行符为实际换行
+    }
+
+    /**
+     * 提供给业务代码获取当前KieContainer的方法
+     */
+    public KieContainer getCurrentKieContainer() {
+        KieContainer container = currentKieContainer.get();
+        if (container == null) {
+            throw new RuntimeException("规则容器未初始化");
+        }
+        return container;
+    }
+}

+ 129 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/rule/RulesController.java

@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.iscs.controller.admin.rule;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.iscs.dal.dataobject.Person;
+import cn.iocoder.yudao.module.iscs.dal.dataobject.rule.RuleDefinitionsDO;
+import cn.iocoder.yudao.module.iscs.service.rule.RuleDefinitionsService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.kie.api.runtime.KieContainer;
+import org.kie.api.runtime.KieSession;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@RestController
+@Tag(name = "管理后台 - uize------------")
+@RequestMapping("/rules")
+public class RulesController {
+
+    @Autowired
+    private KieContainer kieContainer;
+    @Autowired
+    private DynamicRuleManager dynamicRuleManager;
+    @Autowired
+    private RuleDefinitionsService ruleDefinitionsService;
+
+    @Operation(summary = "规则--------")
+    @PostMapping("/execute")
+    public Person executeRule(@RequestBody Person person) {
+        KieSession session = null; // 声明在外部以便在 finally 块中访问
+        try {
+            session = kieContainer.newKieSession();
+            session.insert(person);      // 插入事实对象到工作内存
+            session.fireAllRules();      // 触发所有匹配的规则
+            return person;               // 返回可能已被修改的对象
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to execute rules", e);
+        } finally {
+            if (session != null) {
+                session.dispose(); // &#9989; 显式释放资源
+            }
+        }
+    }
+
+    // 所有规则判断
+    @Operation(summary = "规则2--------")
+    @PostMapping("/execute2")
+    public Person executeRule2(@RequestBody Person person) {
+        KieContainer currentKieContainer = dynamicRuleManager.getCurrentKieContainer();
+        KieSession session = null; // 声明在外部以便在 finally 块中访问
+        try {
+            session = currentKieContainer.newKieSession();
+            session.insert(person);      // 插入事实对象到工作内存
+            session.fireAllRules();      // 触发所有匹配的规则
+            return person;               // 返回可能已被修改的对象
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to execute rules", e);
+        } finally {
+            if (session != null) {
+                session.dispose(); // &#9989; 显式释放资源
+            }
+        }
+    }
+
+
+    // 所有规则中匹配规则名称进行判断
+    @Operation(summary = "规则3--------")
+    @PostMapping("/execute3")
+    public Person executeRule3(@RequestBody Person person) {
+        KieContainer currentKieContainer = dynamicRuleManager.getCurrentKieContainer();
+        KieSession session = null;
+        try {
+            session = currentKieContainer.newKieSession();
+            session.insert(person); // 插入事实对象
+
+            // 关键:使用AgendaFilter过滤,只执行名称为"onrule"的规则
+            session.fireAllRules(match -> {
+                // 获取当前规则的名称(DRL中定义的 rule "onrule" 中的名称)
+                String ruleName = match.getRule().getName();
+                // 只执行名称为"onrule"的规则
+                return ruleName.contains("checkHighSalary1");
+            });
+
+            return person;
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to execute rules", e);
+        } finally {
+            if (session != null) {
+                session.dispose(); // 释放资源
+            }
+        }
+    }
+
+    // 所有规则中匹配规则名称进行判断
+   /* @Operation(summary = "规则4--------")
+    @PostMapping("/execute4")
+    public Person executeRule4(@RequestBody Person person) {
+        KieContainer currentKieContainer = dynamicRuleManager.getCurrentKieContainer();
+        KieSession session = null;
+        try {
+            session = currentKieContainer.newKieSession();
+            session.insert(person); // 插入事实对象
+
+            // 关键:使用AgendaFilter过滤,只执行名称为"onrule"的规则
+            session.fireAllRules(match -> {
+                // 获取当前规则的名称(DRL中定义的 rule "onrule" 中的名称)
+                String ruleName = match.getRule().getResource().getSourcePath();
+                // 只执行名称为"onrule"的规则
+                return ruleName.contains("checkHighSalary1");
+            });
+
+            return person;
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to execute rules", e);
+        } finally {
+            if (session != null) {
+                session.dispose(); // 释放资源
+            }
+        }
+    }*/
+
+
+    @Operation(summary = "规则查询--------")
+    @GetMapping("/getRuleByName")
+    public CommonResult<RuleDefinitionsDO> getRuleByName(String name) {
+        return success(ruleDefinitionsService.getOneByName(name));
+    }
+}

+ 50 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/dal/dataobject/rule/RuleDefinitionsDO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.iscs.dal.dataobject.rule;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 角色和岗位关联 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("rule_definitions")
+@KeySequence("rule_definitions_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RuleDefinitionsDO extends BaseDO {
+
+    /**
+     * 序号ID
+     */
+    @TableId
+    private Long id;
+    /**
+     * name
+     */
+    private String name;
+    /**
+     * description
+     */
+    private String description;
+    /**
+     * version
+     */
+    private Integer version;
+    /**
+     * content
+     */
+    private String content;
+    /**
+     * enabled
+     */
+    private Integer enabled;
+
+}

+ 15 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/dal/mysql/rule/RuleDefinitionsMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.iscs.dal.mysql.rule;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.iscs.dal.dataobject.rule.RuleDefinitionsDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface RuleDefinitionsMapper extends BaseMapperX<RuleDefinitionsDO> {
+
+
+}

+ 19 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/rule/RuleDefinitionsService.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iscs.service.rule;
+
+import cn.iocoder.yudao.module.iscs.dal.dataobject.rule.RuleDefinitionsDO;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * 角色和岗位关联 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface RuleDefinitionsService extends IService<RuleDefinitionsDO> {
+
+    RuleDefinitionsDO getOneByName(String name);
+
+    List<RuleDefinitionsDO> getAllValidRules();
+
+}

+ 37 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/rule/RuleDefinitionsServiceImpl.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iscs.service.rule;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.iscs.dal.dataobject.rule.RuleDefinitionsDO;
+import cn.iocoder.yudao.module.iscs.dal.mysql.rule.RuleDefinitionsMapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * 角色和岗位关联 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class RuleDefinitionsServiceImpl extends ServiceImpl<RuleDefinitionsMapper, RuleDefinitionsDO> implements RuleDefinitionsService {
+
+    @Resource
+    private RuleDefinitionsMapper ruleDefinitionsMapper;
+
+
+    @Override
+    public RuleDefinitionsDO getOneByName(String name) {
+        Assert.notBlank(name, "名称不能为空!");
+        return getOne(Wrappers.<RuleDefinitionsDO>lambdaQuery().eq(RuleDefinitionsDO::getName, name));
+    }
+
+    @Override
+    public List<RuleDefinitionsDO> getAllValidRules() {
+        return list();
+    }
+}

+ 2 - 2
yudao-module-iscs/src/main/resources/rules/sample.drl

@@ -7,12 +7,12 @@ rule "checkHighSalary"
 when
     $p : Person(age > 30 && salary >= 10000)
 then
-    System.out.println("警告:[" + $p.getName() + "]薪资过高!");
+    System.out.println("警告:[" + $p.getName() + "]薪资过高drl!");
 end
 
 rule "checkHighSalary2"
 when
     $p : Person(age > 20 && salary >= 12000)
 then
-    System.out.println("警告:[" + $p.getName() + "]薪资过高2!");
+    System.out.println("警告:[" + $p.getName() + "]薪资过高2drl!");
 end