Spring Boot | 集成Drools规则引擎、动态执行规则
0、前言
我们在体检时通常会做身高体重测量项目,这时体检报告上会有BMI这一体检项。BMI是国际上常用的衡量人体胖瘦程度以及是否健康的一个标准。BMI的计算公式如下:

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、添加规则

使用接口工具(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());
   }
 }
上面的代码中已经给每一步都添加了注释,触发规则的整个流程图如下:

2.10 测试生成结论接口

接口地址:http://127.0.0.1:10007/exam/getConclusion
请求参数:
 {
     "ruleName": "bmi",
     "bmi": 27.51
 }
响应结果:
 {
   "result": "超重"
 }
转载自:https://juejin.cn/post/7306457908636483611




