2025-08-16-Sat-T-Drools规则引擎

规则引擎 Drools

1. 简介

业务规则引擎,业务规则管理系统, BRMS。

规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模块编写业务决策(业务规则),由用户或者开发者在需要时进行配置管理。

规则引擎并不是一个具体的技术框架,而是指一类系统,即业务规则管理系统。市面上常见产品有drools,VisualRules,iLog等

规则引擎实现了将业务规则从程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。

1.1 优点

  • 业务规则和系统代码解耦,实现业务规则的集中管理
  • 在不重启服务的情况下,可随时对业务规则进行扩展和维护(热更新)
  • 可以动态修改业务规则,从而快速响应业务方的需求变更,大大提高了生产效率。

1.2 应用场景

  • 风控系统:风险贷款、风险评估
  • 反欺诈项目:银行贷款、征信验证
  • 决策平台:财务计算
  • 促销平台:满减、打折

1.3 快速案例

  • 引入maven依赖
<!--drools-->
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>7.50.0.Final</version>
</dependency>

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-mvel</artifactId>
    <version>7.50.0.Final</version>
</dependency>
  • 定义Entity
package com.learning.drools.entity;

import lombok.Data;

@Data
public class Order {
    private Double originalPrice;
    private Double realPrice;
}

  • 定义kmodule.xml

在resources/META-INF文件夹下创建kmodule.xml文件

<?xml version="1.0" encoding="UTF-8" ?>

<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <!--name:指定kbase名称,任意但必须唯一, packages:规则文件的目录(在resources目录下), default: 指定当前kbase是否为默认-->
    <kbase name="myKbase1" packages="rules" default="true">
        <!--name: 指定session名称,任意但必须唯一, default: 当前session是否为默认-->
        <ksession name="ksession-rule" default="true"/>
    </kbase>
</kmodule>
  • 定义drl规则文件

在resource下的rules文件夹下定义discountRules.drl文件

package rules
import com.learning.drools.entity.Order // 引入entity


// 第一条规则: 所购图书少于100元,没有优惠
rule "rule_discount_1"
    when
        $order:Order(originalPrice < 100)  // 模式匹配,到规则引擎中(工作内存)查找Order对象,查找originalPrice < 100 的对象; $order是对这个对象的别名

    then
        $order.setRealPrice($order.getOriginalPrice());
        System.out.println("规则1:没有优惠");
    end

// 规则二: 价格在100 - 200 优惠20元

rule "rule_discount_2"
    when
        $order:Order(originalPrice >=100 && originalPrice < 200)
    then
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("规则2:价格在100 - 200, 优惠20元");
    end

// 规则三: 价格在200 - 300 优惠50元

rule "rule_discount_3"
    when $order:Order(originalPrice >=200 && originalPrice < 300)
    then
        $order.setRealPrice($order.getOriginalPrice() - 50);
        System.out.println("规则三:价格在200 - 300, 优惠50元");
    end

// 规则四: 价格在300 以上优惠100元

rule "rule_discount_4"
    when $order:Order(originalPrice >= 300)
    then $order.setRealPrice($order.getOriginalPrice() - 100);
        System.out.println("规则四: 价格在300以上, 优惠100元");
    end

1.4 规则引擎的构成

  • Working Memory(工作内存)
  • Rule Base(规则库)
  • Inference Engine(推理引擎)
    • Pattern Matcher(匹配器) // 匹配符合条件的规则,将其加入到议程中
    • Agenda(议程) // 即将执行的规则就在议程中
    • Execution Engine(执行引擎)

1.5 规则引擎相关概念说明

1.5.1 Working memory

工作内存: drools会从工作内存中获取数据并和规则文件中定义的规则进行模式匹配,所以我们开发的应用程序只需要将我们的数据插入到working Memory中即可。 例如kiesession.insert(order)

1.5.2 Fact

事实: 是指在drools规则应用中,将一个普通的Java Bean 插入到工作内存中后的对象就是Fact对象。

1.5.3 Rule Base

规则库: 在规则文件中定义的规则都会被加载到规则库中。

1.5.4 Pattern Matcher

匹配器: 将Rule Base中的规则与工作内存中的Fact对象进行匹配,成功匹配的规则放入Agenda中。

1.5.5 Agenda

议程:用于存放通过匹配器进行模式匹配后被激活的规则

1.5.6 Execution Engine

执行引擎: 执行Agenda中被激活的规则。

1.6 规则引擎执行的过程

  1. 将初始数据(Fact)输入到工作内存中
  2. 使用匹配器将规则库中的规则和Fact比较
  3. 如果执行规则存在冲突,即同时激活了多个规则,将冲突的规则放入冲突集合
  4. 解决冲突,将激活的规则按照顺序放入Agenda
  5. 执行Agenda中的规则,重复2~5,直到执行完Agenda中所有的规则。

1.7 Kie介绍

KIE(Knowledgek Is Everything)

2. Drools基础语法

2.1 关键字

  • package:包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用
  • import:用于导入类或者静态方法
  • global:全局变量
  • function: 自定义函数
  • query: 查询
  • rule end : 规则体

Drools支持的规则文件,除了drl形式,还有excel 文件类型的。

2.2 规则体语法结构

规则体是规则文件内容中的重要组成部分,是进行业务规则判断,处理业务结果的部分。

规则体语法结构如下:

rule "rule_name"
	attributes
	when
		LHS(Left hand side) // 为空的话,默认为true
    then
    	RHS(Right hand side) 
end

2.3 注释

  • 单行://
  • 多行: /* */

2.4 Pattern 模式匹配

Pattern语法结构为: 绑定的变量名: Object(Field约束)

官方建议绑定的变量名使用$开头

例如: $order:Order(originalPrice < 100) // 需要满足工作内存中存在Order对象, 并且这个Order对象的orginalPrice属性的值小于100

LHS部分可以定义多个pattern,多个pattern可以使用and或者or连接,例如:

rule "rule_discount_2"
    when
        $order:Order(originalPrice >=100 && originalPrice < 200) and
        $order:Order(originalPrice == 200) or
        $order:Order(realPrice == 1000)
    then
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("规则2:价格在100 - 200, 优惠20元");
end

2.5 比较操作符

符号 说明
>
<
>=
<=
==
!=
contains 检查一个Fact对象的某个属性值是否包含一个指定的对象值
not contains 检查一个Fact对象的某个属性值是否不包含一个指定的对象值
memberOf 判断一个Fact对象的某个属性是否在一个或多个集合中
not memberOf 判断一个Fact对象的某个属性是否不在一个或多个集合中
matches 判断一个Fact对象的属性是否与提供的标准Java正则表达式进行匹配
not matches

语法

// contains | not contains
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] not contains value)

// memeberOf | not memberOf
Object(feild memberOf value[Collection/Array])
Object(feild not memberOf value[Collection/Array])

// matches | not matches
Object(feild matches "正则表达式")
Object(feild not matches "正则表达式")

案例

  • 创建一个Java实体类Fact
package com.learning.drools.entity;
import lombok.Data;
import java.util.List;

@Data
public class ComparisonOperatorEntity {
    private String names;
    private List<String> list;
}

  • 定义drl文件
package rules
import com.learning.drools.entity.ComparisonOperatorEntity

// 测试contains
rule "rule1"
    when
        ComparisonOperatorEntity(names contains "judy") //后面没有用到,所以不用绑定变量名
        ComparisonOperatorEntity(list contains  names) // 不写or或者and默认为and关系
    then
        System.out.println("规则1触发:contains");
end

// 测试memberOf
rule "rule2"
    when
        ComparisonOperatorEntity("tome" memberOf list)
    then
        System.out.println("规则2: memberOf");
end

// 测试matches

rule "rule3"
    when
        ComparisonOperatorEntity(names matches "ju.*")
    then
        System.out.println("规则3: matches");
end
  • 测试
@Test
void  testOperator(){
    //获取kieService
    KieServices kieServices = KieServices.Factory.get();
    //获取container
    KieContainer kContainer = kieServices.newKieClasspathContainer();
    // 获取session
    KieSession kieSession = kContainer.newKieSession();

    ComparisonOperatorEntity fact = new ComparisonOperatorEntity();
    fact.setNames("judy");
    fact.setList(new ArrayList<>(){
        {
            add("judy");
            add("tome");
            add("张三");
        }
    });

    kieSession.insert(fact);
    kieSession.fireAllRules();
    kieSession.dispose();
}

2.6 执行指定的规则

  • 指定规则触发条件:rule name
@Test
void  testCertainRule(){
    //获取kieService
    KieServices kieServices = KieServices.Factory.get();
    //获取container
    KieContainer kContainer = kieServices.newKieClasspathContainer();
    // 获取session
    KieSession kieSession = kContainer.newKieSession();

    ComparisonOperatorEntity fact = new ComparisonOperatorEntity();
    fact.setNames("judy");
    fact.setList(new ArrayList<>(){
        {
            add("judy");
            add("tome");
            add("张三");
        }
    });

    kieSession.insert(fact);
    // kieSession.fireAllRules();
    // kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule1"));  // 按照名称选择规则执行
    // kieSession.fireAllRules(new RuleNameMatchesAgendaFilter("rule[1-2]")); // 按照正则表达式执行
    kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("rule")); // 按照以rule开头的规则名选择执行
    kieSession.dispose();
}

2.7 Drools内置方法

规则文件的RHS部分的主要作用是通过插入,删除或者修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。

Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配。原来没有匹配成功的规则在此修改之后有可能就会匹配成功了。

  • 创建实体类
package com.learning.drools.entity;

import lombok.Data;
@Data
public class Student {
    private int id;
    private String name;
    private int age;
}

  • 创建规则文件
    • 常见内置方法
      • update:更新fact,导致重新匹配规则
      • insert:插入一个fact对象到工作内存,导致规则重新匹配
      • retract:删除工作内存中的数据,导致规则重新匹配
package rules

import com.learning.drools.entity.Student

// 测试update内置方法
rule "stu_rule1"
    when
        $s:Student(age >= 14 && age < 18)
    then
        $s.setAge(18);
        update($s); // update 更新了Fact对象,会导致规则重新匹配。所以之后stu_rule2也会执行。如果没有update,age = 15 触发了rule1但是不会触发rule2规则
        System.out.println("规则1触发:" + $s);
end

// 测试update内置方法,
rule "stu_rule2"
    when
        $s:Student(age >= 18)
    then
        $s.setName("成年人:" + $s.getName());
        System.out.println("规则2触发:" + $s);
end

// 用于测试insert
rule "stu_rule3"
    when
        $s:Student(id == 3 || name == "tom")
    then
        Student jerrySon = new Student();
        jerrySon.setAge(18);
        insert(jerrySon);
        System.out.println("规则3执行" + jerrySon); 
end


// 用于测试retract
rule "stu_rule4"
    when
        $s:Student(id == 3 || name == "tom")
    then
        retract($s);
        System.out.println("规则4执行");
end

// 用于测试retract
rule "stu_rule5"
    when
        $s:Student(id == 3 || name == "tom") // 由于stu_rule4在工作内存中删除了这个fact,因此此规则匹配失败
    then
        retract($s);
        System.out.println("规则5执行");
end

  • 测试
    @Test
    void testFunction(){
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kContainer = kieServices.newKieClasspathContainer();
        KieSession kieSession = kContainer.newKieSession();

        Student student = new Student();
        student.setId(1);
        student.setName("tom");
        student.setAge(15);

        kieSession.insert(student);
        kieSession.fireAllRules();
        kieSession.dispose();
    }

3. 规则属性

规则体的构成如下:

rule "rule_name"
	attributes
	when
		LHS
    then
    	RHS
end

Drools提供的属性如下表

属性名 说明
salience 指定规则的执行优先级,salience 10, 数字越大,优先级越高,越先执行 salience 11
dialect 指定规则使用的语言类型,取值为Java或mvel dialect java
enabled 指定规则是否启用 enabled true
date-effective 指定规则生效的时间 data-effective 'yyyy-MM-dd HH:mm:ss' Java代码中设置System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");
date-expires 指定规则失效时间 date-expires 'date'
activation-group 激活分组,具有相同分组名称的规则只能有一个规则触发 activation-group 'group1'
agenda-group 议程分组,只有获取焦点的组中的规则才有可能触发 agenda-group 'group2'
获取焦点: java 代码或规则文件kieSession.getAgenda().getAgendaGroup("group2").setFocus(); auto-focus true
timer 定时器,指定规则触发的时间:
方式一:timer(int: t1 t2) t1表示几秒后执行,t2表示每隔几秒执行一次,t2是可选参数 timer(3s 2s)
方式二:timer(cron: 秒分时日月周[年]) timer(cron: 0/1 * * * * ?)
auto-focus 自动获取焦点,一般结合agenda-group一起使用 auto-focus true 组内只要有一个规则配置了,其他规则也会生效
no-loop 防止死循环 no-loop true

4. Drools高级语法

  • package:包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用
  • import:用于导入类或者静态方法
  • global:全局变量
  • function: 自定义函数
  • query: 查询
  • rule end : 规则体

Drools支持的规则文件,除了drl形式,还有excel 文件类型的。

4.1 Global 全局变量

global关键字用于在规则文件中定义全局变量, 语法结构为 global 对象类型 对象名称

注意事项:

  1. 对于包装类型,在一个规则中修改了global的值,只针对该规则生效
  2. 如果是集合类型或者Java Bean时,在一个规则中修改了global的值,对于Java代码和所有的规则都生效。
global java.lang.Integer count

4.2 query查询

query查询提供了一种查询工作内存中符合约束条件的Fact对象的简单方法。它仅包含规则文件中的LHS部分,不用指定when 和then并且以end结束。语法结构如下:

query "查询名称" 
	LHS
end

例子

// test query

package rules
import com.learning.drools.entity.Student


query "query_1"
    $s:Student(age == 18)
end

query "query_2" (String studentName)
    $s:Student(age < 18 && studentName == "张三")
end
void testQuery() throws InterruptedException {

    System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");
    KieServices kieServices = KieServices.Factory.get();
    KieContainer kContainer = kieServices.newKieClasspathContainer();
    KieSession kieSession = kContainer.newKieSession();
    //kieSession.setGlobal("num",1);
    kieSession.setGlobal("name","张三");
    Student student = new Student();
    student.setId(1);
    student.setName("tom");
    student.setAge(18);
    kieSession.insert(student);
    System.out.println(kieSession.getQueryResults("query_1").size());
    System.out.println("==================");
    Student student1 = new Student();
    student1.setId(2);

    student1.setName("张三");
    student1.setAge(15);
    kieSession.insert(student1);
    System.out.println(kieSession.getQueryResults("query_2", "张三").size());

    kieSession.dispose();
}

4.3 function 函数

function关键字可以在规则文件中定义函数,就相当于Java类中的方法一样。可以在规则体中调用定义的函数。使用函数的好处使可以将业务逻辑集中放置到一个地方,根据需要可以对函数进行修改。函数定义的语法结构如下:

function 返回值类型 函数名(可选参数){
	//逻辑代码
}

例子:

package rules
import com.learning.drools.entity.Student

function String sayHello(String name){
    System.out.println("函数调用");
    return "hello" + name;
}


rule "rule_function"
    when
        $s:Student(age > 60)
    then
        String res = sayHello($s.getName());
        System.out.println("规则 then内容 : " + res);
end

4.4 LHS加强

4.4.1 复合值限制in / not in

类似于SQL中的in

$s:Student(name in ("张三","李四","王五"));
$s:Student(name not in ("张三","李四","王五"));

4.4.2 条件元素eval

eval用于规则体的LHS部分,并返回一个Boolean类型的值。

eval(true)
eval(false)
eval(1 == 1)

4.4.3 条件元素 not

not用于判断工作内存中是否存在某个Fact对象,如果不存在则返回true,如果存在则返回false。

rule "lhs_not"
    when
        not Student(age < 38)
    then
        System.out.println("大于38");
end

4.4.4 条件元素exists

与not相反

rule "lhs_not"
    when
        exists Student(age < 38)
    then
        System.out.println("小于38");
end

使用exists与不使用exists区别

exists Student() //只会触发一次
Student() //可能触发多次

4.4.5 规则继承

规则之间可以使用extends关键字进行规则条件部分的继承,类似于Java类之间的继承

例如

package rules
import com.learning.drools.entity.Student

rule "ext_1"
    when 
        Student(age < 50)  // # 只有when部分会被继承
    then
        System.out.println("规则1触发"); // # then部分不会被继承
end

rule "ext_2" extends "ext_1" // # 继承了ext_1, 所以只有ext_1条件满足时才会执行ext_2
    when
		Student(age > 40) 
    then
        System.out.println("规则2触发");
end

4.5 RHS 加强

RHS是规则体的重要组成部分,当LHS部分的条件匹配成功后,对应的RHS部分就会触发执行。一般在RHS部分中需要进行业务处理。

在RHS部分Drools为我们提供了一个内置对象,名称就是drools。本小节介绍几个drools对象提供的方法

4.5.1 halt

halt方法用于立即终止后面所有规则的执行

rule "rhs_halt1"
    when
    then
        System.out.println("halt1规则触发");
        drools.halt(); // 当前方法执行后,后续规则终止执行
end

rule "rhs_halt2"
    when
    then
        System.out.println("halt2规则触发");
end

4.5.2 getWorkingMemory

getWorkingMembory方法用于返回工作内存对象

rule "rhs_getWM"
    when
    then
        System.out.println("工作内存对象:" + drools.getWorkingMemory().getFactCount());
end

4.5.3 getRule

getRule方法的作用是返回规则对象本身

rule "rhs_getRule"
    when
    then
        System.out.println("规则本身" + drools.getRule().getName());
end

4.6 规则编码规范

  • 所有的规则文件(drl)应统一放在一个规定的文件夹中,如何/rules文件夹
  • 书写的每个规则应尽量加上注释。注释要清晰明了,言简意赅
  • 同一个类型的对象尽量放在一个规则文件中,如所有Student类型的对象尽量放在一个规则文件中
  • 规则结果部分(RHS)尽量不要有条件语句,如(if...)。尽量不要有复杂的逻辑和深层的嵌套语句
  • 每个规则最好都加上salience属性,明确执行顺序
  • Drools默认dialect为java,避免使用mvel

5. SpringBoot整合Drools

  • pom
<?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.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.learning.drools</groupId>
    <artifactId>drools</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>drools</name>
    <description>drools</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>



        <!--drools-->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

<!--        <dependency>-->
<!--            <groupId>org.drools</groupId>-->
<!--            <artifactId>drools-mvel</artifactId>-->
<!--            <version>7.50.0.Final</version>-->
<!--        </dependency>-->
<!--        -->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-templates</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-beans</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
            </exclusions>
            <version>7.50.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>annotationProcessor</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  • config
package com.learning.drools.config;

import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DroolsConfig {

    private final KieServices kieServices = KieServices.Factory.get();

    @Bean
    @ConditionalOnMissingBean // 先查找再创建
    public KieBase kieBase(){
        System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");

        KieContainer kContainer = kieServices.newKieClasspathContainer();
        return kContainer.getKieBase();
    }

    @Bean
    @ConditionalOnMissingBean // 先查找再创建
    public KieContainer kieContainer(){
        return kieServices.newKieClasspathContainer();
    }
}

  • service
@Service
public class RuleService {
    @Autowired
    private KieBase kieBase;
    public void rule(){
        KieSession kieSession = kieBase.newKieSession();
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("hello"));
        kieSession.dispose();
    }
}
  • controller
@RestController
public class RuleController {

    @Autowired
    private RuleService ruleService;

    @GetMapping("/rule")
    public String rule() {
        ruleService.rule();
        return "success";
    }

}

6. WorkBench

Drools是整个KIE项目中的一个组件,Drools还包括一个Drools-WB的模块,它是一个可视化的规则编辑器。

使用workbench可以在浏览器中创建数据对象,创建规则文件,创建测试场景并将规则部署到maven仓库供其他应用使用。

docker pull jboss/business-central-workbench:7.52.0.Final
docker run -p 8080:8080 -p 8001:8001 -d --name workbench jboss/business-central-workbench:7.50.0.Final
  • 创建用户
# 进入bin,为Kie-Server添加一个具有kie-server角色的用户
./add-user.sh -a -u kieserver -p kieserver1! -g kie-server

./add-user.sh -a -u workbench -p workbench! -g admin,kie-server,rest-all
# 默认角色有
  ROLE        DESCRIPTION
  *************************************************
  admin       The administrator
  analyst     The analyst
  kiemgmt     KIE management user
  rest-all    Rest API permissions
  Accounting  Accounting role
  PM          Project manager role
  HR          Human resources role
  sales       Sales role
  IT          IT role

6.1 使用方式

  • 创建空间、项目
  • 创建数据对象
  • 创建DRL规则文件
  • 创建测试场景
  • 设置KieBase和KieSession

kieBase的软件包就是drl的package地址

kiesession有两种状态

  1. stateful

  2. stateless

  • 编译、构建、部署
  • 在项目中使用部署的规则

7. 案例

7.1 个人所得税计算

7.1.1 计算规则

规则编号 名称 描述
1 计算应纳税所得额 税前工资 - 5000
2 设置税率,应纳税所得额 <= 1500 税率0.03, 速算扣除数为0
3 设置税率,应纳税所得额 <= 4500 税率0.1, 速算扣除数为105
4 设置税率,应纳税所得额 <= 9000 税率0.2, 速算扣除数为555
5 设置税率,应纳税所得额 <= 35000 税率0.25, 速算扣除数为1005
6 设置税率,应纳税所得额 <= 55000 税率0.3, 速算扣除数为2755
7 设置税率,应纳税所得额 <= 80000 税率0.35, 速算扣除数为5505
8 设置税率,应纳税所得额 > 80000
9 计算税后工资 扣税额 = 应缴纳税所得额*税率 - 速算扣除数
税后工资 = 税前工资 - 扣税额

7.1.2 编写drl

package rules

import com.learning.drools.owntax.entity.Calculation

rule "r1"

    when
    then
        System.out.println("ok");
end

// 第一类: 计算应纳税所得额
// 第二类: 计算税
// 第三类: 计算税后工资

/**
    private double wage; // 税前工资
    private double wageMore;  // 应纳税所得额
    private double cess;// 税率
    private double preMinus; //速算扣除数
    private double wageMinus; // 扣税额
    private double actualWage; //税后工资
*/
rule "计算应纳税所得额"
    salience 100
    date-effective "2022-09-01"
    no-loop true // 防止更改后发生死循环
    when
        $cal:Calculation(wage > 5000)
    then
        $cal.setWageMore($cal.getWage() - 3500);
        update($cal); // 工作内存中更新
end

rule "设置税率,应纳税所得额 <= 1500"
    salience 99
    date-effective "2022-09-01"
    activation-group "SetCessGroup" // 保证计算税率的规则只能有一个规则被触发
    no-loop true
    when
        $cal:Calculation(wageMore > 0 && wageMore <= 1500)
    then
        $cal.setCess(0.03);
        $cal.setPreMinus(0);
        $cal.setWageMinus($cal.getWageMore() * $cal.getCess() - $cal.getPreMinus());
        update($cal); // 工作内存中更新
end

rule "设置税率,应纳税所得额 <= 4500"
        salience 98
        date-effective "2022-09-01"
        activation-group "SetCessGroup" // 保证计算税率的规则只能有一个规则被触发
        no-loop true
        when
            $cal:Calculation(wageMore > 1500 && wageMore <= 4500)
        then
            $cal.setCess(0.1);
            $cal.setPreMinus(105);
            $cal.setWageMinus($cal.getWageMore() * $cal.getCess() - $cal.getPreMinus());
            update($cal); // 工作内存中更新
end

rule "设置税率,应纳税所得额 <= 9000"
        salience 97
        date-effective "2022-09-01"
        activation-group "SetCessGroup" // 保证计算税率的规则只能有一个规则被触发
        no-loop true
        when
            $cal:Calculation(wageMore > 4500 && wageMore <= 9000)
        then
            $cal.setCess(0.2);
            $cal.setPreMinus(555);
            $cal.setWageMinus($cal.getWageMore() * $cal.getCess() - $cal.getPreMinus());
            update($cal); // 工作内存中更新
end

rule "设置税率,应纳税所得额 <= 35000"
        salience 96
        date-effective "2022-09-01"
        activation-group "SetCessGroup" // 保证计算税率的规则只能有一个规则被触发
        no-loop true
        when
            $cal:Calculation(wageMore > 9000 && wageMore <= 35000)
        then
            $cal.setCess(0.25);
            $cal.setPreMinus(1005);
            $cal.setWageMinus($cal.getWageMore() * $cal.getCess() - $cal.getPreMinus());
            update($cal); // 工作内存中更新
end

rule "设置税率,应纳税所得额 <= 55000"
        salience 95
        date-effective "2022-09-01"
        activation-group "SetCessGroup" // 保证计算税率的规则只能有一个规则被触发
        no-loop true
        when
            $cal:Calculation(wageMore > 35000 && wageMore <= 55000)
        then
            $cal.setCess(0.3);
            $cal.setPreMinus(2755);
            $cal.setWageMinus($cal.getWageMore() * $cal.getCess() - $cal.getPreMinus());
            update($cal); // 工作内存中更新
end

rule "设置税率,应纳税所得额 <= 80000"
        salience 94
        date-effective "2022-09-01"
        activation-group "SetCessGroup" // 保证计算税率的规则只能有一个规则被触发
        no-loop true
        when
            $cal:Calculation(wageMore > 55000 && wageMore <= 80000)
        then
            $cal.setCess(0.5);
            $cal.setPreMinus(5505);
            $cal.setWageMinus($cal.getWageMore() * $cal.getCess() - $cal.getPreMinus());
            update($cal); // 工作内存中更新
end

rule "计算税后工资"
        salience 93
        date-effective "2022-09-01"
        no-loop true
        when
            $cal:Calculation(wage > 0)
        then
            $cal.setActualWage($cal.getWage() - $cal.getWageMinus());
            update($cal); // 工作内存中更新
end

7.1.3 配置

@Configuration
public class DroolsConfig {
    private final KieServices kieServices;

    DroolsConfig(){
        System.setProperty("drools.dateformat","yyyy-MM-dd");
        kieServices = KieServices.Factory.get();

    }

    @Bean
    @ConditionalOnMissingBean
    public KieBase kieBase() {
        KieContainer kieContainer = kieServices.newKieClasspathContainer();
        return kieContainer.getKieBase();
    }

}
<!--resource/META-INF/kmodule.xml-->
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <!--name:指定kbase名称,任意但必须唯一, packages:规则文件的目录, default: 指定当前kbase是否为默认-->
    <kbase name="myKbase1" packages="rules" default="true">
        <!--name: 指定session名称,任意但必须唯一, default: 当前session是否为默认-->
        <ksession name="ksession-rule" default="true"/>
    </kbase>
</kmodule>

7.1.4 java代码

  • controller
@RestController
public class OwnTaxController {


    @Autowired
    private OwnTaxService ownTaxService;

    @GetMapping("/calc/{wage}")
    public Calculation calc( @PathVariable Double wage) {
        return ownTaxService.CalculateOwnTax(wage);
    }
}
  • service
@Service
public class OwnTaxService {


    @Autowired
    private KieBase kieBase;

    public Calculation CalculateOwnTax(Double wage) {
       // Calculation calculation = Calculation.builder().wage(wage).build();
        Calculation calculation = new Calculation();
        calculation.setWage(wage);

        KieSession kieSession = kieBase.newKieSession();
        kieSession.insert(calculation);
        kieSession.fireAllRules();
        kieSession.dispose();
        return calculation;
    }
}

  • entity
package com.learning.drools.owntax.entity;
import lombok.Data;

@Data
public class Calculation {
    private double wage; // 税前工资
    private double wageMore;  // 应纳税所得额
    private double cess;// 税率
    private double preMinus; //速算扣除数
    private double wageMinus; // 扣税额
    private double actualWage; //税后工资
}

7.2 信用卡申请

7.2.1 计算规则

  • 合法性检查
规则编号 名称 描述
1 检查学历与薪水 如果申请人没房没车,同时学历大专以下,月薪少于5000,那么不通过
2 检查学历与薪水 如果申请人没房没车,同时学历大专或本科,并且月薪少于3000,那么不通过
3 检查学历与薪水 如果申请人没房也没车,同时学历为本科以上,并且月薪少于2000,同时之前没有信用卡的,那么不通过
4 检查申请人已有的信用卡数量 如果申请人现有的信用卡数量大于10,那么不通过
  • 信用卡额度
规则编号 名称 描述
1 规则1 如果申请人有房有车,或者月收入在20000以上,那么额度为15000
2 规则2 没房没车,收入在10000 ~ 20000,额度为6000
3 规则3 没房没车,收入在10000以下,额度为3000
4 规则4 有房没车或者有车没房,收入在10000以下,额度为5000
5 规则5 有房没车或者有车没房,收入在10000~20000,额度为8000

7.2.2 配置

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--drools-->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-templates</artifactId>
            <version>7.50.0.Final</version>
        </dependency>

    </dependencies>
<?xml version="1.0" encoding="UTF-8" ?>
<!--resource/META-INF/kmodule.xml-->
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <!--name:指定kbase名称,任意但必须唯一, packages:规则文件的目录, default: 指定当前kbase是否为默认-->
    <kbase name="myKbase1" packages="rules" default="true">
        <!--name: 指定session名称,任意但必须唯一, default: 当前session是否为默认-->
        <ksession name="ksession-rule" default="true"/>
    </kbase>
</kmodule>
@Configuration
public class DroolsConfig {
    private final KieServices kieServices;

    DroolsConfig(){
        System.setProperty("drools.dateformat","yyyy-MM-dd");
        kieServices = KieServices.Factory.get();

    }

    @Bean
    @ConditionalOnMissingBean
    public KieBase kieBase() {
        KieContainer kieContainer = kieServices.newKieClasspathContainer();
        return kieContainer.getKieBase();
    }

}

7.2.3 编写drl

package rules
import com.learning.drools.owntax.entity.Person


// 合法性检查
rule "检查学历或薪水1"
    salience 100
    activation-group "credit_check"
    no-loop true
    when
        $person:Person(!hasHouse && !hasCar && salary < 5000 && educationCode < 1)
    then
        $person.setHasCredit(false);
        update($person)
        drools.halt(); // 审核不通过直接退出
end

rule "检查学历或薪水2"
    salience 99
    activation-group "credit_check"
    no-loop true
    when
        $person:Person(!hasHouse && !hasCar && salary < 3000 && (educationCode == 1 || educationCode == 2))
    then
        $person.setHasCredit(false);
        update($person)
        drools.halt(); // 审核不通过直接退出
end

rule "检查学历或薪水3"
    salience 98
    activation-group "credit_check"
    no-loop true
    when
        $person:Person(!hasHouse && !hasCar && salary < 2000 && educationCode > 2 && creditCardNum <= 0)
    then
        $person.setHasCredit(false);
        update($person)
        drools.halt(); // 审核不通过直接退出
end

rule "检查学历或薪水4"
    salience 97
    activation-group "credit_check"
    no-loop true
    when
        $person:Person(creditCardNum > 10)
    then
        $person.setHasCredit(false);
        update($person)
        drools.halt(); // 审核不通过直接退出
end

// 信用卡额度

rule "信用卡额度规则1"
    salience 90
    activation-group "credit_quota"
    no-loop true
    when
        $person:Person( hasCredit && (hasCar && hasHouse || salary > 20000) )
    then
        $person.setQuota(15000.0);
        update($person)
        System.out.println($person);
end

rule "信用卡额度规则2"
    salience 89
    activation-group "credit_quota"
    no-loop true
    when
        $person:Person( hasCredit && (!hasCar && !hasHouse && (salary >= 10000 && salary <= 20000)) )
    then
        $person.setQuota(6000.0);
        update($person)
end

rule "信用卡额度规则3"
    salience 88
    activation-group "credit_quota"
    no-loop true
    when
        $person:Person( hasCredit && (!hasCar && !hasHouse && salary < 10000) )
    then
        $person.setQuota(3000.0);
        update($person)
end

rule "信用卡额度规则4"
    salience 87
    activation-group "credit_quota"
    no-loop true
    when
        $person:Person( hasCredit && (hasCar || hasHouse) && salary < 10000)
    then
        $person.setQuota(5000.0);
        update($person)
end

rule "信用卡额度规则5"
    salience 86
    activation-group "credit_quota"
    no-loop true
    when
        $person:Person( hasCredit && (hasCar || hasHouse) && ( salary > 10000 && salary <= 20000))
    then
        $person.setQuota(8000.0);
        update($person)
end

7.2.4 Java代码

@Service
public class CredCardService {

    @Autowired
    private KieBase kieBase;

    public Person executeRule(Person person){
        KieSession kieSession = kieBase.newKieSession();
        kieSession.insert(person);
        kieSession.fireAllRules();
        kieSession.dispose();
        return person;
    }
}

7.3 保险产品准入规则

7.3.1 决策表

Drools除了支持drl形式的文件外,还支持xls格式的文件(即,excel文件)。这种xls格式的文件通常称为决策表(decision table)。

决策表是一个“精确而紧凑的”表示条件逻辑的方式,非常适合商业级别的规则。决策表与现有的drl文件可以无缝替换。Drools提供了响应的API可以将xls文件编译为drl格式的字符串。

决策表语法:

关键字 说明 是否必须
RuleSet 相当于drl文件中的package 必须,只能有一个。如果没有设置RuleSet对应的值,则使用默认rule_table
Sequential 取值为Boolean,true表示规则从上到下顺序执行,false表示乱序 可选
Import 相当于drl中的import,用逗号分隔 可选
Variables 相当于drl 中的global,如果有多个全局变量则中间用逗号分隔 可选
RuleTable 它指示了后面将会有一批rule,RuleTable的名称将会作为以后生成rule的前缀 必须
CONDITION 规则条件关键字,相当于drl文件中的when。下面两行则表示LHS部分,第三行为注释行,从第四行开始,每一行代表一条规则 每一个规则至少有一个
ACTION 相当于drl中的then 每个规则至少一个
NO-LOOP 可选
AGENDA-GROUP 议程分组,只有获取焦点的组中的规则才有可能触发 agenda-group 'group2'
获取焦点: java 代码或规则文件kieSession.getAgenda().getAgendaGroup("group2").setFocus(); auto-focus true
可选

在决策表中经常使用到占位符,语法为$number,用于替换每条规则中设置的具体值。

7.3.2 测试案例

  • 准备决策表

  • 格式转换
public static KieSession getKieSessionByXML(String realPath) throws FileNotFoundException {
    File file = new File(realPath);
    FileInputStream fileInputStream = new FileInputStream(file);
    SpreadsheetCompiler compiler = new SpreadsheetCompiler();
    String compile = compiler.compile(fileInputStream, InputType.XLS);
    System.out.println(compile);

    KieHelper kieHelper = new KieHelper();
    kieHelper.addContent(compile, ResourceType.DRL);
    return kieHelper.build().newKieSession();
}
  • 测试
@Test
void contextLoads() throws FileNotFoundException {

    KieSession kieSessionByXML = KieSessionUtils.getKieSessionByXML("D:\\Users\\fei\\gitrepo\\owntax\\src\\main\\resources\\rules\\decisionTable.xls");
    Person person = new Person();
    person.setAge(20);
    person.setSalary(34.0);
    kieSessionByXML.insert(person);
    kieSessionByXML.fireAllRules();
    kieSessionByXML.dispose();

}

7.3.3 规则说明

以下规则全部满足才能准入成功

规则编号 说明
1 保险公司是:PICC
2 销售区域是:北京、天津
3 投保年龄是:0 ~17 岁
4 保险期间是:20,25,30年
5 缴费方式是:一次性交清或年缴
6 保险期与缴费期规则一:保险期间为20年,缴费期间最长10年缴费,且不能选择一次性交清
7 保险期与缴费期规则一:保险期间为25年,缴费期间最长15年缴费,且不能选择一次性交清
8 保险期与缴费期规则一:保险期间为30年,缴费期间最长20年缴费,且不能选择一次性交清
9 被保人要求:(投保年龄 + 保险期间)不得大于40周岁
10 保险金额规则:投保时约定,最低5万元,超过的部分必须为1000元的整数倍
11 出单基本保额限额规则:线上出单基本保额限额62.5万元,超过62.5万元需要配合转线下出单

本案例中规则文件是一个excel文件,业务人员可以直接更改这个文件中指标的值,系统不需要做任何变更

7.3.4 配置

同上

7.3.5 编写决策表

7.3.6 Java代码

  • utils
public static KieSession getKieSessionByXML(String realPath) throws FileNotFoundException {
    File file = new File(realPath);
    FileInputStream fileInputStream = new FileInputStream(file);
    SpreadsheetCompiler compiler = new SpreadsheetCompiler();
    String compile = compiler.compile(fileInputStream, InputType.XLS);
    System.out.println(compile);

    KieHelper kieHelper = new KieHelper();

    kieHelper.addContent(compile, ResourceType.DRL);
    Results verify = kieHelper.verify();
    if(verify.hasMessages(Message.Level.ERROR)) {
        System.out.println("规则文件语法异常 Errors found: " + verify.getMessages(Message.Level.ERROR));
    }
    return kieHelper.build().newKieSession();
}
  • service
public List<String> insuranceInfoCheck(InsuranceInfo insuranceInfo) throws Exception {
    KieSession kieSession = KieSessionUtils.getKieSessionByXML("D:\\Users\\fei\\gitrepo\\owntax\\src\\main\\resources\\rules\\insurance.xls");
    kieSession.getAgenda().getAgendaGroup("sign").setFocus();
    kieSession.insert(insuranceInfo);
    ArrayList<String> listRules = new ArrayList<>();
    kieSession.setGlobal("listResults", listRules);
    kieSession.fireAllRules();
    return listRules;
}
posted @ 2025-11-23 22:21  飞↑  阅读(14)  评论(0)    收藏  举报