1 整合规则引擎Drools
1.1 前言
假如有这么个需求,网上购物,需要根据不同的规则计算商品折扣,比如VIP客户增加5%的折扣,购买金额超过1000元的增加10%的折扣等,而且这些规则可能随时发生变化,甚至增加新的规则。面对这个需求,你该怎么实现呢?难道是计算规则一变,就要修改业务代码,重新测试,上线吗。
其实,我们可以通过规则引擎来实现,Drools
就是一个开源的业务规则引擎,可以很容易地与 springboot
应用程序集成,那本文就用 Drools
来实现一下上面说的需求吧。
1.2 pom.xml
我们创建一个spring boot应用程序,pom中添加drools相关的依赖,如下:
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.59.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.59.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>7.59.0.Final</version>
</dependency>
1.3 Drools配置类
创建一个名为 DroolsConfig
的配置 java 类。
@Configuration
public class DroolsConfig {
// 制定规则文件的路径
private static final String RULES_CUSTOMER_RULES_DRL = "rules/customer-discount.drl";
private static final KieServices kieServices = KieServices.Factory.get();
@Bean
public KieContainer kieContainer() {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kieContainer;
}
}
- 定义了一个
KieContainer
的Spring
的Bean
,KieContainer
用于通过加载应用程序的/resources
文件夹下的规则文件来构建规则引擎。 - 创建
KieFileSystem
实例并配置规则引擎并从应用程序的资源目录加载规则的DRL
文件。 - 使用
KieBuilder
实例来构建drools
模块。我们可以使用KieSerive
单例实例来创建KieBuilder
实例。 - 最后,使用
KieService
创建一个KieContainer
并将其配置为spring bean
。
1.4 示例Demo
1.4.1 添加业务Model
创建一个订单对象 OrderRequest
,这个类中的字段后续回作为输入信息发送给定义的drools规则中,用来计算给定客户订单的折扣金额。
@Data
public class OrderRequest {
/**
* 客户号
*/
private String customerNumber;
/**
* 年龄
*/
private Integer age;
/**
* 订单金额
*/
private Integer amount;
/**
* 客户类型
*/
private CustomerType customerType;
}
定义一个客户类型CustomerType 的枚举,规则引擎会根据该值计算客户订单折扣百分比,如下所示。
public enum CustomerType {
LOYAL, NEW, DISSATISFIED;
public String getValue() {
return this.toString();
}
}
最后,创建一个订单折扣类 OrderDiscount
,用来表示计算得到的最终的折扣,如下所示。
@Data
public class OrderDiscount {
/**
* 折扣
*/
private Integer discount = 0;
}
1.4.2 定义drools 规则
前面的 DroolsConfig
类中指定 drools
规则的目录,现在我们在 /src/main/resources/rules
目录下添加customer-discount.drl
文件,在里面定义对应的规则。
import com.alvin.drools.model.OrderRequest;
import com.alvin.drools.model.CustomerType;
global com.alvin.drools.model.OrderDiscount orderDiscount;
dialect "mvel"
// 规则1: 根据年龄判断
rule "Age based discount"
when
// 当客户年龄在20岁以下或者50岁以上
OrderRequest(age < 20 || age > 50)
then
// 则添加10%的折扣
System.out.println("==========Adding 10% discount for Kids/ senior customer=============");
orderDiscount.setDiscount(orderDiscount.getDiscount() + 10);
end
// 规则2: 根据客户类型的规则
rule "Customer type based discount - Loyal customer"
when
// 当客户类型是LOYAL
OrderRequest(customerType.getValue == "LOYAL")
then
// 则增加5%的折扣
System.out.println("==========Adding 5% discount for LOYAL customer=============");
orderDiscount.setDiscount(orderDiscount.getDiscount() + 5);
end
rule "Customer type based discount - others"
when
OrderRequest(customerType.getValue != "LOYAL")
then
System.out.println("==========Adding 3% discount for NEW or DISSATISFIED customer=============");
orderDiscount.setDiscount(orderDiscount.getDiscount() + 3);
end
rule "Amount based discount"
when
OrderRequest(amount > 1000L)
then
System.out.println("==========Adding 5% discount for amount more than 1000$=============");
orderDiscount.setDiscount(orderDiscount.getDiscount() + 5);
end
- 我们使用了一个名为
orderDiscount
的全局参数,可以在多个规则之间共享。 drl
文件可以包含一个或多个规则。我们可以使用mvel
语法来指定规则。此外,每个规则使用rule
关键字进行描述。- 每个规则
when-then
语法来定义规则的条件。 - 根据订单请求的输入值,我们正在为结果添加折扣。如果规则表达式匹配,每个规则都会向全局结果变量添加额外的折扣
1.4.3 添加Service层
创建一个名为OrderDiscountService 的服务类,如下:。
@Service
public class OrderDiscountService {
@Autowired
private KieContainer kieContainer;
public OrderDiscount getDiscount(OrderRequest orderRequest) {
OrderDiscount orderDiscount = new OrderDiscount();
// 开启会话
KieSession kieSession = kieContainer.newKieSession();
// 设置折扣对象
kieSession.setGlobal("orderDiscount", orderDiscount);
// 设置订单对象
kieSession.insert(orderRequest);
// 触发规则
kieSession.fireAllRules();
//或者 通过规则过滤器实现只执行指定规则
//kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("Age based discount"));
// 中止会话
kieSession.dispose();
return orderDiscount;
}
}
- 注入
KieContainer
实例并创建一个KieSession
实例。 - 设置了一个
OrderDiscount
类型的全局参数,它将保存规则执行结果。 - 使用
insert()
方法将请求对象传递给drl
文件。 - 调用
fireAllRules()
方法触发所有规则。 - 最后通过调用
KieSession
的dispose()
方法终止会话。
1.4.4 添加Controller
创建一个名为OrderDiscountController 的Controller类,具体代码如下:
@RestController
public class OrderDiscountController {
@Autowired
private OrderDiscountService orderDiscountService;
@PostMapping("/get-discount")
public ResponseEntity<OrderDiscount> getDiscount(@RequestBody OrderRequest orderRequest) {
OrderDiscount discount = orderDiscountService.getDiscount(orderRequest);
return new ResponseEntity<>(discount, HttpStatus.OK);
}
}
1.4.5 测试
对于年龄 < 20 且金额 > 1000 的 LOYAL 客户类型,我们应该根据我们定义的规则获得 20% 的折扣。
入参:
{
"customerNumber":"123456",
"age":20,
"amount":20000,
"customerType":"LOYAL"
}
出参:
{
"discount": 10
}
参考链接:https://mp.weixin.qq.com/s/SfMhb34dj7DrLvMCKZv9Uw
1.5 drools规则解析
1.5.1 简介
在使用 Drools
时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl
,drl
是Drools Rule Language
的缩写。在规则文件中编写具体的规则内容。
一套完整的规则文件内容构成如下:
关键字 | 描述 |
---|---|
package | 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用 |
import | 用于导入类或者静态方法 |
global | 全局变量 |
function | 自定义函数 |
query | 查询 |
rule end | 规则体 |
Drools支持的规则文件,除了drl形式,还有Excel文件类型的。
1.5.2 规则体语法结构
规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。
规则体语法结构如下:
rule "ruleName"
attributes
when
LHS
then
RHS
end
rule
:关键字,表示规则开始,参数为规则的唯一名称。attributes
:规则属性,是rule
与when
之间的参数,为可选项。when
:关键字,后面跟规则的条件部分。LHS(Left Hand Side)
:是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS
为空,则它将被视为始终为true
的条件元素。
还可以定义多个pattern
,多个pattern
之间可以使用and
或者or
进行连接,也可以不写,默认连接为and
then
:关键字,后面跟规则的结果部分。RHS(Right Hand Side)
:是规则的后果或行动部分的通用名称。end
:关键字,表示一个规则结束
1.5.3 注释
在drl
形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。
单行注释用//
进行标记,多行注释以/
开始,以/
结束。如下示例:
//规则rule1的注释,这是一个单行注释
rule "rule1"
when
then
System.out.println("rule1触发");
end
/*
规则rule2的注释,
这是一个多行注释
*/
rule "rule2"
when
then
System.out.println("rule2触发");
end
1.5.4 Pattern模式匹配
前面我们已经知道了 Drools
中的匹配器可以将 Rule Base
中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。
其中 绑定变量名可以省略
,通常绑定变量名的命名一般建议以 $
开始。如果定义了绑定变量名,就可以在规则体的 RHS
部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。
//所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
//Order为类型约束,originalPrice为属性约束
$order:Order(originalPrice < 200 && originalPrice >= 100)
then
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
$order:Order($op:originalPrice < 200 && originalPrice >= 100)
then
System.out.println("$op=" + $op);
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
LHS部分还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,也可以不写,默认连接为and
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
$order:Order($op:originalPrice < 200 && originalPrice >= 100) and
$customer:Customer(age > 20 && gender=='male')
then
System.out.println("$op=" + $op);
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
1.5.5 比较操作符
Drools提供的比较操作符有:>、<、>=、<=、==、!=、contains 、not contains、memberOf 、not memberOf、matches 、not matches
前6个比较操作符和Java中的完全相同,下面我们重点学习后6个比较操作符。
contains | not contains
语法结构
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] not contains value)memberOf | not memberOf
语法结构
Object(field memberOf value[Collection/Array])
Object(field not memberOf value[Collection/Array])matches | not matches
语法结构
Object(field matches “正则表达式”)
Object(field not matches “正则表达式”)
1.5.6 dialect 属性
drools
支持两种 dialect
:java
和mvel
:
dialect
:缺省为java
,当然我们也推荐统一使用java dialect, 以降低维护成本.dialect
:属性仅用于设定RHS
部分语法,LHS
部分并不受dialect
的影响.
package 和 rule 都可以指定 dialect 属性.
mvel
是一种表达式语言, github主页为 https://github.com/mvel/mvel , 文档主页为 http://mvel.documentnode.com/
dools
中的 mvel dialect
可以认为是 java dialect
的超集, 也就是说 mvel dialect
模式下, 也支持 java dialect
的写法
mvel
和 java
的主要区别:
- 对于
POJO
对象,java dialect
必须使用getter
和setter
方法. - 对于
POJO
对象,mvel dialect
可以直接使用属性名称进行读写, 甚至是private
属性也可以.
rule "java_rule"
enabled true
dialect "java"
when
$order:Order()
then
System.out.println("java_rule fired");
$order.setRealPrice($order.getOriginalPrice()*0.8) ;
end
mvel dialect示例:
rule "mvel_rule"
enabled false
dialect "mvel"
when
$order:Order()
then
System.out.println("mvel_rule fired");
$order.realPrice=$order.originalPrice*0.7 ;
end
原文地址:https://blog.csdn.net/u012060033/article/details/130598925
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_49481.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!