快速集成和使用 drools 规则引擎
规则引擎技术的主要思想是将应用程序中的业务规则分离出来,业务规则不再以程序代码的形式驻留在系统中,而是存储在独立的文件或者数据库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理。业务规则在程序运行时被加载到规则引擎中供应用系统调用。
drools 是当前比较流行的规则引擎技术,由 JBoss 组织提供的基于 Java 语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中(例如数据库),使得业务规则的变更不需要修改项目代码、不需要重启服务器就可以立即生效。
本篇博客的 demo 以个税计算器为例,介绍如何使用 drools 规则引擎,有关具体技术细节,限于篇幅有限,这里不会介绍,具体细节可以参考官网。
drools官网地址:https://drools.org
drools源码下载地址:https://github.com/kiegroup/drools
一、搭建工程
搭建一个 SpringBoot 工程,具体结构如下:
drools 规则引擎将规则编写在以 .drl 为后缀的文件中,drl 文件默认也是使用 Java 语言编写,所以学习起来很容易。
IDEA 自带 drools 插件,在 drl 文件中编写规则时有智能提示效果,非常方便。一般情况下,我们使用 IDEA 编写业务规则,默认情况下 drl 文件会被打包到项目 jar 包中,为了方便后续调整规则,我们可以将 drl 文件的内容,存储到数据库中或者 oss 云盘中,程序在运行时从 jar 包外部读取规则内容。
本篇博客的 demo 没有创建 application.yml 文件,默认情况下 springboot 的 web 启动端口为 8080
我们看一下 pom 文件的内容,对于 drools 来说只需要引入一个包即可:drools-compiler
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jobs</groupId>
<artifactId>springboot_drools</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--drools规则引擎-->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.10.0.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
</project>
二、代码细节展示
本篇博客的 demo 是个税计算器,我们创建一个用于向规则引擎传递数据的实体类
package com.jobs.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;
}
这里不考虑缴纳社保和专项扣除等因素,个税计算的规则如下:
因此我们对应的在 rule.drl 中编写的规则如下:
//当前规则文件用于计算个人所得税
package calculation
import com.jobs.entity.Calculation
//计算应纳税所得额
rule "tax_setWagemore"
salience 100
//设置生效日期
date-effective "2022-10-01"
no-loop true
when
$cal:Calculation(wage > 0)
then
double wagemore = $cal.getWage() - 5000;
$cal.setWagemore(wagemore);
update($cal);
end
//设置税率、速算扣除数
rule "tax_3000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal:Calculation(wagemore <= 3000)
then
$cal.setCess(0.03);//税率
$cal.setPreminus(0);//速算扣除数
update($cal);
end
rule "tax_12000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal:Calculation(wagemore > 3000 &&wagemore <= 12000)
then
$cal.setCess(0.1);//税率
$cal.setPreminus(210);//速算扣除数
update($cal);
end
rule "tax_25000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal : Calculation(wagemore > 12000 &&wagemore <= 25000)
then
$cal.setCess(0.2);
$cal.setPreminus(1410);
update($cal);
end
rule "tax_35000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal : Calculation(wagemore > 25000 &&wagemore <= 35000)
then
$cal.setCess(0.25);
$cal.setPreminus(2660);
update($cal);
end
rule "tax_55000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal : Calculation(wagemore > 35000 &&wagemore <= 55000)
then
$cal.setCess(0.3);
$cal.setPreminus(4410);
update($cal);
end
rule "tax_80000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal : Calculation(wagemore > 55000 &&wagemore <= 80000)
then
$cal.setCess(0.35);
$cal.setPreminus(7160);
update($cal);
end
rule "tax_max"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal : Calculation(wagemore > 80000)
then
$cal.setCess(0.45);
$cal.setPreminus(15160);
update($cal);
end
rule "tax_result"
salience 80
when
$cal : Calculation(wage > 0 && wagemore > 0 && cess > 0)
then
//扣税额
double wageminus = $cal.getWagemore() * $cal.getCess() - $cal.getPreminus();
double actualwage = $cal.getWage() - wageminus;
$cal.setWageminus(wageminus);
$cal.setActualwage(actualwage);
System.out.println("--税前工资:"+$cal.getWage());
System.out.println("--应纳税所得额:"+$cal.getWagemore());
System.out.println("--税率:" + $cal.getCess());
System.out.println("--速算扣除数:" + $cal.getPreminus());
System.out.println("--扣税额:" + $cal.getWageminus());
System.out.println("--税后工资:" + $cal.getActualwage());
end
本篇博客的 demo 在每次被调用时,都去读取 rule.drl 的内容,具体代码在 RuleService 中实现:
package com.jobs.service;
import com.jobs.entity.Calculation;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.internal.utils.KieHelper;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@Service
public class RuleService {
//调用Drools规则引擎实现个人所得税计算
public Calculation calculate(Calculation calculation) throws Exception {
KieSession session = createKieSessionFromDRL("src\\main\\resources\\rule.drl");
session.insert(calculation);
session.fireAllRules();
session.dispose();
return calculation;
}
// 从指定路径的文件中,读取规则内容,创建 KieSession
public KieSession createKieSessionFromDRL(String drlFullPath) throws Exception{
//设置规则所使用的日期格式
System.setProperty("drools.dateformat", "yyyy-MM-dd");
//读取规则文件中的内容,实际项目中,可以把规则内容存储到数据库中,或者 web 服务器上,或者 oss 中
//当前把规则就存放在文件中,在项目运行中,在不重启项目的情况下,可以修改规则内容并立刻生效
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(drlFullPath), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append(System.lineSeparator());
}
}
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(sb.toString(), ResourceType.DRL);
return kieHelper.build().newKieSession();
}
}
然后在 RuleController 中对外提供计算个税的接口,只需要传递一个税前工资额即可计算得出结果
package com.jobs.controller;
import com.jobs.entity.Calculation;
import com.jobs.service.RuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/rule")
public class RuleController {
@Autowired
private RuleService ruleService;
@RequestMapping("/calculate")
public Calculation calculate(double wage) {
try {
Calculation calculation = new Calculation();
calculation.setWage(wage);
calculation = ruleService.calculate(calculation);
System.out.println(calculation);
return calculation;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
为了方便接口调用,这里也提供了前端页面,代码如下:
<!DOCTYPE html>
<html>
<head>
<!-- 页面meta -->
<meta charset="utf-8">
<title>个人所得税计算</title>
<meta name="description" content="个人所得税计算">
<meta name="keywords" content="个人所得税计算">
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
</head>
<body class="mainBg">
<div id="app">
<h3 align="center">个人所得税计算器(使用规则引擎实现)</h3>
<h3 align="center">在项目运行过程中,你可以在不重启项目的情况下,修改规则文件内容,并立刻生效</h3>
<table align="center" width="25%" border="0">
<tr>
<td>税前月收入</td>
<td>
<input type="text" v-model="cal.wage">
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="计 算" @click="calculate()">
</td>
</tr>
<tr>
<td>应纳税所得额</td>
<td>
<input type="text" v-model="cal.wagemore">
</td>
</tr>
<tr>
<td>税率</td>
<td>
<input type="text" v-model="cal.cess">
</td>
</tr>
<tr>
<td>速算扣除数</td>
<td>
<input type="text" v-model="cal.preminus">
</td>
</tr>
<tr>
<td>扣税额</td>
<td>
<input type="text" v-model="cal.wageminus">
</td>
</tr>
<tr>
<td>税后工资</td>
<td>
<input type="text" v-model="cal.actualwage">
</td>
</tr>
</table>
</div>
</body>
<!-- 引入组件库 -->
<script src="js/vue.js"></script>
<script src="js/axios.js"></script>
<script>
new Vue({
el: '#app',
data:{
cal:{}
},
methods: {
calculate(){
if(this.cal.wage <= 5000){
alert("税前月收入需要大于5000");
return;
}
axios.get("/rule/calculate?wage=" + this.cal.wage).then((res) => {
console.log(res);
this.cal = res.data;
});
}
}
});
</script>
</html>
三、验证成果
启动项目后,默认情况下 springboot 允许访问 resources 下的 static 目录下的静态页面 index.html
对于本篇博客的 demo 来说,访问 http://localhost:8080
即可查看到静态页面,输入 10000 元计算个税,如下图:
从结果可以发现,税率是 0.1,执行的是 rule.drl 文件中的名称为 tax_12000 的规则,此时你可以使用 IDEA 修改一下,比如将税率修改为 0.2
rule "tax_12000"
salience 90
no-loop true
activation-group "SETCess_Group"
when
$cal:Calculation(wagemore > 3000 &&wagemore <= 12000)
then
$cal.setCess(0.2);//这里故意将税率修改为 0.2
$cal.setPreminus(210);//速算扣除数
update($cal);
end
注意不要重启 IDEA 的项目,此时重新点击页面中的计算,发现刚刚修改的规则生效了,如下图所示:
OK,以上就是有关 springboot 使用 drools 规则引擎的介绍,有兴趣的话可以下载源代码进行验证。
本篇博客的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/springboot_drools.zip