likes
comments
collection
share

Spring Boot | 集成Drools规则引擎、动态执行规则

作者站长头像
站长
· 阅读数 4

0、前言

我们在体检时通常会做身高体重测量项目,这时体检报告上会有BMI这一体检项。BMI是国际上常用的衡量人体胖瘦程度以及是否健康的一个标准。BMI的计算公式如下:

Spring Boot | 集成Drools规则引擎、动态执行规则

BMI的参考标准如下:

人体胖瘦程度消瘦正常值超重
BMI数值<18.5kg/m²18.5-24kg/m²>24kg/m²

下面,我们通过Drools规则引擎实现通过BMI衡量人体胖瘦程度的功能。

1、Drools概述

Drools是一个基于Java的开源规则引擎,用于管理和执行业务规则。

2、集成Drools

2.1、创建示例项目

使用 IDEA 创建 Spring Boot 项目,项目技术选型大体如下:

  • spring boot 2.7.7
  • drools 7.73.0.Final
  • mybatis plus 3.5.3.1
  • druid 1.2.16
  • mysql 8

2.2、引入Maven依赖

在 Spring Boot 中集成 Drools,需要在 pom.xml 文件中引入所需的依赖,具体代码如下:

 <properties>
     <drools.version>7.73.0.Final</drools.version>
 </properties>
 ​
 <dependencies>
   <!-- 其它依赖 -->
   <dependency>
     <groupId>org.drools</groupId>
     <artifactId>drools-core</artifactId>
     <version>${drools.version}</version>
   </dependency>
   <dependency>
     <groupId>org.drools</groupId>
     <artifactId>drools-compiler</artifactId>
     <version>${drools.version}</version>
   </dependency>
   <dependency>
     <groupId>org.drools</groupId>
     <artifactId>drools-mvel</artifactId>
     <version>${drools.version}</version>
   </dependency>
   <dependency>
     <groupId>org.kie</groupId>
     <artifactId>kie-api</artifactId>
     <version>${drools.version}</version>
   </dependency>
 </dependencies>

2.3、创建规则实体类

创建实体类Rule.java,实体类代码如下:

 @Data
 @TableName("sys_rule")
 public class Rule implements Serializable {
 ​
   private static final long serialVersionUID = 1L;
 ​
   /**
    * ID
    */
   @TableId(type = IdType.ASSIGN_ID)
   private String id;
   /**
    * 规则名称
    */
   private String name;
   /**
    * 规则内容
    */
   private String content;
   /**
    * 创建人
    */
   private String createBy;
   /**
    * 创建时间
    */
   private Date createTime;
   /**
    * 更新人员
    */
   private String updateBy;
   /**
    * 更新时间
    */
   private Date updateTime;
 }

实体类对应数据表sys_rule表结构:

 CREATE TABLE `sys_rule`  (
   `id` varchar(36) NOT NULL COMMENT 'ID',
   `name` varchar(256) NOT NULL COMMENT '规则名称',
   `content` text COMMENT '规则内容',
   `create_by` varchar(256) COMMENT '创建者',
   `update_by` varchar(256) COMMENT '更新者',
   `create_time` datetime COMMENT '创建日期',
   `update_time` datetime COMMENT '更新时间',
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB COMMENT = '规则表';

主要字段是规则名称(name)、规则内容(content)。稍后,我们会在 Service 类中定义一个方法,用于根据规则名称获取规则信息。

2.4、创建规则数据访问对象

RuleDao.java —— 数据访问对象

 public interface RuleDao extends BaseMapper<Rule> {
 }

RuleDao接口继承 MyBatis Plus 的BaseMapper接口,对规则的增、删、改、查主要是通过 MyBatis Plus 提供的增、删、改、查默认方法实现。

2.5、创建规则Service

RuleService.java —— Service接口

 public interface RuleService extends IService<Rule> {
   /**
    * 根据规则名称获取规则信息
    * @param name 规则名称
    */
   Rule getByName(String name);
 }

在 Service接口 中,我们定义了一个方法:

  • getByName():根据规则名称获取规则信息;

RuleServiceImpl.java —— Service实现类

 @Service
 public class RuleServiceImpl extends ServiceImpl<RuleDao, Rule> implements RuleService {
   /**
    * 根据规则名称获取规则实体
    * @param name 规则名称
    * @return /
    */
   @Override
   public Rule getByName(String name) {
     return getOne(Wrappers.<Rule>lambdaQuery().eq(Rule::getName, name));
   }
 }

getByName()方法的实现没什么好说的,使用过 MyBatis Plus 的朋友一看就明白。

2.6、创建规则Controller

RuleController.java —— 控制器类

 @Slf4j
 @RestController
 @RequestMapping("/rule")
 public class RuleController {
 ​
   @Resource
   private RuleService ruleService;
 ​
   @PostMapping("/save")
   public Object save(HttpServletRequest request) {
     // 规则名称
     String name = request.getParameter("name");
     MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
     // 规则文件
     MultipartFile multipartFile = multipartRequest.getFile("file");
 ​
     Rule rule = new Rule();
     rule.setName(name);
     rule.setContent(getContent(multipartFile));
     rule.setCreateBy("wanggc");
     rule.setCreateTime(new Date());
 ​
     ruleService.save(rule);
 ​
     return Dict.create().set("msg", "添加成功。");
   }
 ​
   private String getContent(MultipartFile multipartFile) {
     String content = null;
     try (InputStream inputStream = multipartFile.getInputStream()) {
       content = IoUtil.read(inputStream, StandardCharsets.UTF_8);
     } catch (IOException e) {
       log.error("读取文件内容失败,异常信息:", e);
     }
     return content;
   }
 ​
 }

规则控制器中目前只实现了一个添加规则的接口。添加规则接口的流程大体可以概括为:从request中获取到规则名称,读取上传文件的内容,然后一同保存到数据表中。

2.7、添加规则

Spring Boot | 集成Drools规则引擎、动态执行规则

使用接口工具(ApiPost7)测试添加规则接口。

接口地址:http://127.0.0.1:10007/rule/save

请求参数:

  • name - 规则名称,字符串类型,后续调用规则时也是使用这个名称;
  • file - 规则文件,drl文件;

响应结果:

 {
   "msg": "添加成功。"
 }

bmi.drl 规则文件的具体内容如下:

 package cn.ddcherry.springboot.demo;
 ​
 dialect "java"
 ​
 import cn.ddcherry.springboot.demo.fact.ExamData;
 ​
 rule "bmi_1"
     no-loop true
     lock-on-active true
     salience 1
     when
         $item:ExamData(bmi < 18.5);
     then
         $item.setConclusion("消瘦");
 end
 ​
 rule "bmi_2"
     no-loop true
     lock-on-active true
     salience 1
     when
         $item:ExamData(bmi >= 18.5 && bmi <= 24);
     then
         $item.setConclusion("正常");
 end
 ​
 rule "bmi_3"
     no-loop true
     lock-on-active true
     salience 1
     when
         $item:ExamData(bmi > 24);
     then
         $item.setConclusion("超重");
 end

规则文件说明:

  • package - 用于定义规则文件的命名空间,可以不和物理路径一致;
  • import- 用于导入其他Java类或包,以便在规则文件中使用它们;
  • rule - 规则名称,规则名称需要保持全局惟一;
  • no-loop - 规则的属性,表示该规则不会重复执行;
  • lock-on-active - 规则的属性,表示该规则不会重复执行;
  • salience - 规则的属性,用于设置规则的优先级,数值越大,优先级越高,默认值为0;
  • when - 条件语句,用于指定执行后续的动作语句需要满足什么条件;
  • then - 规则的动作语句,表示满足条件时需要执行的操作;
  • end - 规则结束;

2.8、创建规则事实对象

ExamData.java —— 规则事实对象

 @Data
 public class ExamData {
   /**
    * BMI的测量值
    */
   private Double bmi;
   /**
    * 规则名称
    */
   private String ruleName;
   /**
    * 结论
    */
   private String conclusion;
 }

事实对象就是一个普通的JavaBean,规则引擎可以对事实对象进行任意的读写操作。

2.9、创建体检业务控制器类

ExamController.java —— 体检业务控制器类

 @Slf4j
 @RestController
 @RequestMapping("/exam")
 public class ExamController {
 ​
   @Resource
   private RuleService ruleService;
 ​
   @PostMapping("/getConclusion")
   public Object save(@RequestBody ExamData examData) {
     // 根据规则名称获取规则实体
     Rule rule = ruleService.getByName(examData.getRuleName());
     try {
       // 初始化KieHelper,用于构建规则
       KieHelper kieHelper = new KieHelper();
       // 设置规则内容
       kieHelper.addContent(rule.getContent(), ResourceType.DRL);
       // 创建KieSession对象
       KieSession kieSession = kieHelper.build().newKieSession();
       // 插入事实对象
       kieSession.insert(examData);
       // 触发规则
       kieSession.fireAllRules();
       // 释放KieSession资源
       kieSession.dispose();
     } catch (Exception e) {
       log.error("生成结论时发生异常,异常信息:", e);
     }
     Dict result = Dict.create();
     return Objects.isNull(examData.getConclusion()) ? result.set("msg", "生成结论失败!") : result.set("result", examData.getConclusion());
   }
 }

上面的代码中已经给每一步都添加了注释,触发规则的整个流程图如下:

Spring Boot | 集成Drools规则引擎、动态执行规则

2.10 测试生成结论接口

Spring Boot | 集成Drools规则引擎、动态执行规则

接口地址:http://127.0.0.1:10007/exam/getConclusion

请求参数:

 {
     "ruleName": "bmi",
     "bmi": 27.51
 }

响应结果:

 {
   "result": "超重"
 }