What&Why Drools?
Drools(JBoss Rules )
的前身是Codehaus的一个开源项目叫Drools,后来纳入JBoss门下,更名为JBoss Rules,成为了JBoss
应用服务器的规则引擎。- Drools是一个易于访问企业策略、易于调整以及易于管理的
开源业务规则引擎
,符合业内标准,特点就是速度快
、效率高
。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。 - 还在使用复杂的
JAVA代码
校验复杂的优惠券/超市打折/计价规则/商品定价/阶梯定价/游戏规则/业务规则
?试试用Drools来解救代码吧,适用但不仅仅包含以上场景。 - 相关资料 Drools Docs,Drools Spring Integration
KIE是什么?
跟Drools相关,一直有一个叫KIE的东西,会经常见到,这里有必要解释一下。
KIE
(Knowledge Is Everything,知识就是一切。我们用的包是其中针对Spring优化的Kie-Spring
)是一个伞形项目,旨在将我们的相关技术集中在一起。它也是我们项目之间共享的核心
。
-
KIE包含以下不同但相关的项目,为业务自动化和管理提供完整的解决方案组合:
-
Drools是一个
业务规则管理系统
,具有前向链接和后向链接推理的规则引擎,允许快速可靠地评估业务规则和复杂的事件处理。规则引擎也是创建专家系统的基本构建块,在人工智能中,该专家系统是模拟人类专家的决策能力的计算机系统。 -
jBPM是一个灵活的
业务流程管理套件
,允许您通过描述为实现这些目标而需要执行的步骤来建模您的业务目标。 -
OptaPlanner是一种
约束求解器
,可优化员工排班,车辆路径,任务分配和云优化等用例。 -
Business Central是一个
功能齐全的Web应用程序
,用于自定义业务规则和流程的可视化组合。 -
UberFire是一个基于
Web的工作台框架
,受Eclipse富客户端平台的启发。
Drools语言
rule "name"
attributes
when
LHS
Then
RHS
end
一个规则通常包括四个部分:
- 规则名称(rule name)
- 属性部分(attribute)
- 条件部分(LHS)
- 结果处理部分(RHS)
也就是说一大堆的if-else都是不需要的,用规则通杀一切。
条件连接符
对于对象内部的多个约束/条件的连接,可以采用 &&
(and)、||
(or) 和 ,
(and)来进行实现。
虽然&&和,运算符具有相同的语义,但它们的解析具有不同的优先级: &&
运算符在 ||
之前。并且 ,
与 &&
和||
不能混合使用,也就是说在有&&
或||
出现的LHS 当中,是不可以有 ,
连接符出现,反之亦然。
比较操作符
共提供了十二种类型的比较操作符,分别是:>
、>=
、<
、<=
、= =
、!=
、contains
、not contains
、memberof
、not memberof
、matches
、not matches
;
取值/赋值
-
$taxiRide:TaxiRide(distanceInMile <= 3)
可以获取传递进来的对象taxiRide,用$taxiRide.getDistanceInMile()
则可以获取到对象里面的属性值 -
如果要直接给传递进来的对象赋值,则要用
:=
进行处理,用=
是没用的,当然,对于普通的操作则无需特殊处理。
背景
下面的项目会用到这个规则,这里作为一个背景简单介绍一下,我们只搞起步价,续租价,返空费
三个。
A市巡游出租汽车收费标准2018:
(一)起步价:首3公里12元;
(二)续租价:超过3公里部分,每公里2.6元;
(三)候时费:巡游车营运时速低于10公里,每小时44元;
(四)返空费实行阶梯附加,15至25公里按照续租价加收20%,25公里以上按续租价加收50%;
(五)夜间服务费(23:00-次日5:00),按续租价加收30%。
开源项目
- https://github.com/moshowgame/spring-cloud-study
- https://github.com/moshowgame/spring-cloud-study/tree/master/spring-cloud-study-drools
项目结构
Maven依赖
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.24.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.24.0.Final</version>
</dependency>
Application.yml
- Application.yml,并没有什么要配置的,当然,可以把自己一些变量配置进来再用@Value去取。
server:
port: 9999
servlet:
context-path: /drools
spring:
http:
encoding:
force: true
charset: UTF-8
application:
name: spring-cloud-study-drools
Config配置文件
- TaxiFareConfiguration
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.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.softdev.system.demo.service")
public class TaxiFareConfiguration {
private static final String drlFile = "TAXI_FARE_RULE.drl";
@Bean
public KieContainer kieContainer() {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(drlFile));
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
KieModule kieModule = kieBuilder.getKieModule();
return kieServices.newKieContainer(kieModule.getReleaseId());
}
}
Entity
两个实体:
- TaxiFare 打车费。
import java.math.BigDecimal;
import lombok.Data;
@Data
public class TaxiFare {
private BigDecimal nightSurcharge;
private BigDecimal rideFare;
public BigDecimal total() {
return this.nightSurcharge.add(this.rideFare);
}
}
- TaxiRide 打车情况,isNightSurcharge是否夜间,distanceInMile打车距离。
import java.math.BigDecimal;
import lombok.Data;
@Data
public class TaxiRide {
private Boolean isNightSurcharge;
private BigDecimal distanceInMile;
}
Service
- TaxiFareCalculatorService
import java.math.BigDecimal;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.softdev.system.demo.entity.TaxiFare;
import com.softdev.system.demo.entity.TaxiRide;
@Service
public class TaxiFareCalculatorService {
@Autowired
private KieContainer kieContainer;
public BigDecimal calculateFare(TaxiRide taxiRide, TaxiFare rideFare) {
KieSession kieSession = kieContainer.newKieSession();
kieSession.setGlobal("rideFare", rideFare);
kieSession.insert(taxiRide);
kieSession.fireAllRules();
kieSession.dispose();
return rideFare.total();
}
}
Controller
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.softdev.system.demo.entity.TaxiFare;
import com.softdev.system.demo.entity.TaxiRide;
import com.softdev.system.demo.service.TaxiFareCalculatorService;
@RequestMapping("taxi")
@RestController
/**
* Drools的士计费控制器
* (起步价:首3公里12元; 续租价:超过3公里部分,每公里2.6元;返空费实行阶梯附加,15至25公里按照续租价加收20%,25公里以上按续租价加收50%; )
* @author zhengkai.blog.csdn.net
* */
public class TaxiController {
@Autowired
private TaxiFareCalculatorService taxiFareCalculatorService;
@GetMapping("cal")
public ResponseEntity<Object> calTaxiFare(TaxiRide taxiRide){
//http://localhost:9999/drools/taxi/cal
if(taxiRide.getIsNightSurcharge()==null) {taxiRide.setIsNightSurcharge(false);}
if(taxiRide.getDistanceInMile()==null) {taxiRide.setDistanceInMile(new BigDecimal(9));}
TaxiFare rideFare = new TaxiFare();
//Drools计算
BigDecimal totalCharge = taxiFareCalculatorService.calculateFare(taxiRide, rideFare);
//BigDecimal处理:设置一位小数位,向上取整,去掉末尾0,,转换为String
String totalString = totalCharge.setScale(1,BigDecimal.ROUND_UP).stripTrailingZeros().toPlainString();
return ResponseEntity.ok(totalString);
}
}
Drools规则文件
- TAXI_FARE_RULE.drl ,放在
resources
目录下即可
global com.softdev.system.demo.entity.TaxiFare rideFare;
import com.softdev.system.demo.entity.TaxiRide;
import java.math.BigDecimal;
dialect "mvel"
/**
* 的士打表计价Drools
* @Author by zhengkai.blog.csdn.net
*/
rule "Calculate Taxi Fare - Output "
salience 100
when
$taxiRide:TaxiRide();
then
System.out.println("#公里数 : "+$taxiRide.getDistanceInMile);
System.out.println("#起步价 : "+12);
rideFare.setNightSurcharge(BigDecimal.ZERO);
rideFare.setRideFare(BigDecimal.ZERO);
end
rule "Calculate Taxi Fare - Less than three kilometers"
salience 99
when
//起步价:首3公里12元; 不论白天黑夜 || distanceInMile = 3
$taxiRide:TaxiRide(distanceInMile <= 3);
then
rideFare.setRideFare(new BigDecimal(12));
end
rule "Calculate Taxi Fare - 3 ~ 15 kilometers"
salience 99
when
//续租价:超过3公里部分,每公里2.6元; 非夜间
$taxiRide:TaxiRide( isNightSurcharge == false , distanceInMile > 3 , distanceInMile <= 15);
then
BigDecimal secondFare = ($taxiRide.getDistanceInMile().subtract(new BigDecimal(3))).multiply(new BigDecimal(2.6));
System.out.println("#续租价 : "+secondFare);
rideFare.setRideFare(new BigDecimal(12).add(secondFare));
System.out.println("#应付金额 : "+rideFare.getRideFare());
end
rule "Calculate Taxi Fare - 15~25 kilometers"
salience 99
when
//续租价:超过3公里部分,每公里2.6元;
//返空费实行阶梯附加,15至25公里按照续租价加收20%
$taxiRide:TaxiRide( isNightSurcharge == false , distanceInMile > 15 , distanceInMile <= 25);
then
BigDecimal secondFare = ($taxiRide.getDistanceInMile().subtract(new BigDecimal(15))).multiply(new BigDecimal(2.6)).multiply(new BigDecimal(1.2));
System.out.println("#续租价 : "+secondFare);
//12+13x2.6
rideFare.setRideFare(new BigDecimal(45.8).add(secondFare));
System.out.println("#应付金额 : "+rideFare.getRideFare());
end
测试规则
#公里数 : 18
#起步价 : 12
#续租价 : 9.3599999999999999733546474089962311969192616506432313240715207582065549019034733646549284458160400390625
#应付金额 (加上前15公里45.8元): 55.1599999999999971311837043685954877124221913381432313240715207582065549019034733646549284458160400390625
#浏览器输出:55.2
#公里数 : 2
#起步价 : 12
#浏览器输出:12
#公里数 : 3.5
#起步价 : 12
#续租价 : 1.3000000000000000444089209850062616169452667236328125
#应付金额 : 13.3000000000000000444089209850062616169452667236328125
#浏览器输出:13.3