欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

规则引擎的应用

程序员文章站 2022-05-07 21:54:58
...

分享一个应用实例https://www.ibm.com/developerworks/cn/java/j-drools/

自己在项目中用到的案例

项目场景介绍

法律业务,分为律师端和用户端,用户下单咨询,律师解答。律师有自己的擅长领域(对应用户下单的业务类型),

律师有工作地点,对应用户的地址。用了下了一条订单,系统会根据业务类型匹配和地址匹配,分配给特定的律师,

律师看得订单可以进行解答。

一 jar包 

		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.4.10</version>
		</dependency>

		<!-- drools 规则引擎  -->
		<dependency>
			<groupId>org.kie</groupId>
			<artifactId>kie-api</artifactId>
			<version>${drools.version}</version>
		</dependency>


		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-compiler</artifactId>
			<version>${drools.version}</version>
		</dependency>

二  表结构

订单表

CREATE TABLE `constant_message_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `order_no` varchar(50) DEFAULT NULL COMMENT '订单号',
  `out_order_no` varchar(50) DEFAULT NULL COMMENT '外部系统订单号',
  `out_order_type` tinyint(3) DEFAULT NULL COMMENT '外部订单分类',
  `order_status` tinyint(3) DEFAULT '0' COMMENT '订单状态(0:待解答,2:解答中, 3:已回答待审核,4:审核通过待评价,5:审核失败待评价,6:服务完成(用户评价完成))',
  `isolation_status` tinyint(3) DEFAULT '0' COMMENT '隔离状态(0:未隔离,1:已隔离)',
  `front_status` tinyint(3) DEFAULT '1' COMMENT '前端状态(0:不显示,1:显示)',
  `title` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  `consult_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  `consult_datetime` datetime DEFAULT NULL COMMENT '咨询时间',
  `answer_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  `answer_datetime` datetime DEFAULT NULL COMMENT '答复时间',
  `first_submit_time` datetime DEFAULT NULL COMMENT '首次提交时间',
  `answer_template_id` int(11) DEFAULT NULL COMMENT '回答模板id',
  `consult_user_id` int(11) DEFAULT NULL COMMENT '咨询用户id',
  `product_id` int(11) DEFAULT NULL COMMENT '产品id',
  `channel_id` int(11) DEFAULT NULL COMMENT '渠道id',
  `is_open` tinyint(3) DEFAULT '1' COMMENT '是否公开到互联网(0:不公开,1:公开)',
  `service_personal_id` int(11) DEFAULT NULL COMMENT '服务人员id(资源)',
  `process_mode` tinyint(3) DEFAULT NULL COMMENT '处理方式(预留)',
  `grap_order_datetime` datetime DEFAULT NULL COMMENT '接单时间',
  `business_id` int(11) DEFAULT NULL COMMENT '业务类型id',
  `isolation_warning_cause` tinyint(3) DEFAULT NULL COMMENT '隔离预警原因',
  `isolation_warning_remark` varchar(2000) DEFAULT '' COMMENT '举报原因备注',
  `isolation_datetime` datetime DEFAULT NULL COMMENT '隔离时间',
  `check_status` tinyint(3) DEFAULT '0' COMMENT '审核状态(0:待审核,1:审核通过,2:审核失败)',
  `check_opinion` varchar(2000) DEFAULT NULL COMMENT '审核意见',
  `check_people_id` int(11) DEFAULT NULL COMMENT '审核人员id',
  `check_datetime` datetime DEFAULT NULL COMMENT '审核时间',
  `order_issue` varchar(1000) DEFAULT '' COMMENT '订单下发地',
  `order_source` tinyint(3) DEFAULT '0' COMMENT '订单来源(0:web,1:app)',
  `order_type` tinyint(3) DEFAULT '0' COMMENT '订单类型(0:系统派单,1:兜底派单,2:下级解答)',
  `parent_order_id` int(11) DEFAULT NULL COMMENT '父订单id',
  `time_out_status` tinyint(3) DEFAULT '0' COMMENT '超时状态(0:未超时,1:第一超时时间超时,2:第三超时时间超时)',
  `answer_terrace` tinyint(3) DEFAULT NULL COMMENT '答复平台(0:web,1:app)',
  `province_id` int(11) DEFAULT NULL COMMENT '省id',
  `province_name` varchar(200) DEFAULT NULL COMMENT '省名称',
  `city_id` int(11) DEFAULT NULL COMMENT '市id',
  `city_name` varchar(200) DEFAULT NULL COMMENT '市名称',
  `region_id` int(11) DEFAULT NULL COMMENT '区id',
  `region_name` varchar(200) DEFAULT NULL COMMENT '区名称',
  `the_last_round_of_fallback_time` datetime DEFAULT NULL COMMENT '上一轮进入兜底超时时间点',
  `third_laywer_name` varchar(30) DEFAULT NULL COMMENT '第三方律师名称',
  `create_datetime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_datetime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_boss_designate` tinyint(3) DEFAULT '0' COMMENT '是否由boss指派(0:否,1:是)',
  `is_delete` tinyint(3) DEFAULT '0' COMMENT '是否删除(0:否,1:是)',
  `isolation_reason` varchar(1000) DEFAULT '' COMMENT '隔离原因',
  `isolation_reason_remark` varchar(1000) DEFAULT '' COMMENT '隔离原因备注',
  `is_inform_laywer` tinyint(3) DEFAULT '0' COMMENT '是否提醒律师解答(0:未提醒,1已提醒)',
  `cancel_reason` varchar(1000) DEFAULT '' COMMENT '取消订单的原因',
  `is_file_away` tinyint(3) DEFAULT '0' COMMENT '是否归档(0:未归档,1:归档)',
  `conform_rules_resource` text COMMENT '符合规则的律师id(逗号隔开)',
  `execute_rule_count` int(11) DEFAULT '0' COMMENT '执行规则次数',
  PRIMARY KEY (`id`),
  KEY `product_id` (`product_id`),
  KEY `service_personal_id` (`service_personal_id`),
  KEY `order_status` (`order_status`),
  KEY `isolation_status` (`isolation_status`) USING BTREE,
  KEY `business_id` (`business_id`) USING BTREE,
  KEY `time_out_status` (`time_out_status`) USING BTREE,
  KEY `consult_user_id` (`consult_user_id`) USING BTREE,
  KEY `id` (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=346 DEFAULT CHARSET=utf8 COMMENT='及时咨询订单';

核心字段为conform_rules_resource ,会根据提供的规则查询到符合条件的律师,在订单表中插入起id

 规则表

CREATE TABLE `rule_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `rule_name` varchar(200) DEFAULT NULL COMMENT '规则名称',
  `rule_content` text COMMENT '规则内容',
  `is_available` tinyint(3) DEFAULT NULL COMMENT '是否启用(1:启用,0:未启用)',
  `remark` varchar(2000) DEFAULT NULL COMMENT '备注',
  `create_datetime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_datetime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_delete` tinyint(3) DEFAULT NULL COMMENT '是否删除(0:未删除,1:已删除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='规则信息表';

核心字段:rule_content 根据规则内容去操作规则。

还有产品表,用来绑定rule_id,用户在下单前会选择产品,产品绑定规则,下单后根据所绑定的规则进行分配

还有律师与业务类型的关系表,根据业务类型规则,找到指定的律师。

三 代码演示

前台添加规则内容,即保存到rule_info表中的rule_content字段

内容如下

package promotion.orderallocation;
import com.bestone.fw.model.mybatis.gen.entity.ConstantMessageOrder
import com.bestone.fw.model.drools.entity.TargetSql;
//订单分配律师规则
rule "rule001"
 lock-on-active true
 salience 5
                  when
                      $messageOrder : ConstantMessageOrder(provinceId != null)
                      $targetSql : TargetSql()
                  then
                  $targetSql.setContent(" rio.administrative_division_id = " + $messageOrder.getProvinceId() );
                  System.out.println($targetSql.getContent());
                  update($targetSql);
   end
rule "rule002"
 lock-on-active true
 salience 4
       when
           $messageOrder : ConstantMessageOrder( businessId != null)
           $targetSql : TargetSql()
       then
             $targetSql.setContent(" FIND_IN_SET( " + $messageOrder.getBusinessId() + ",rio.business_type_ids) ");
             update($targetSql);

   end

 其实这也就是规则中的drl文件的内容,你也可以写在drl文件中,后台在去读取drl文件资源。但是我们的业务需求是每个订单产品对应一个规则,所以不能去读取特定文件,这样子不够灵活。如果采用文件的方式,当你修改产品的规则时,要去修改配置文件,相对比较麻烦,不利于维护。所以通过数据库表的形式,去关联产品后规则内容,后台拿到规则内容后,也是通过创建一个临时的drl文件,然后把规则内容写进drl文件中,分配完之后删除这个临时文件。

有人会有疑问:为什么规则内容还是一串代码?这让维护人员很头疼,还要去了解数据库中的字段。其实可以根据需求,做个图形化页面出来,操作之后保存这段规则内容。

下面解释一些规则内容,这里有2条规则,when是规则条件 then是符合条件后要执行的sql内容。

第一条规则是省份id存在时,则满足rio表中的行政区域id=省份id。

第二条是 订单对象中有业务类型businessId 那么执行sql条件语句 FIND_IN_SET($messageOrder.getBusinessId(),rio.business_type_ids)   messageOrder.getBusinessId() 是订单表中用户下单后保存的业务类型id,rio.business_type_ids 是律师与业务类型关系表中的业务类型id数组。

        String content = ruleInfo.getRuleContent().replaceAll("\n","\r\n");
        ValueOperations<String , Object> kieBaseRedis = redisTemplate.opsForValue();
        KieHelper helper = new KieHelper();
        helper.addContent(content, ResourceType.DRL);
        KieBase kieBase =  helper.build();
        kieBaseRedis.set(CommonConstants.DROOLS_KIE_BASE + ruleInfo.getId(), kieBase);

保存后,讲规则存入redis中。

 

用户下单规则分配

Sql对象,保存处理后的sql语句list


public class TargetSql implements Serializable {

    private List<String> content;

    public List<String> getContent() {
        return content;
    }

    public void setContent(String contentStr) {
        if(null == content){
             content = new ArrayList<>() ;
        }
        this.content.add(contentStr);
    }
}
Integer reuleId = ruleInfos.get(0).getId();

        ValueOperations<String , Object> kieBaseRedis = redisTemplate.opsForValue();
        KieBase kieBase = (KieBase)  kieBaseRedis.get(CommonConstants.DROOLS_KIE_BASE + reuleId);

        if(null == kieBase){
            RuleInfoQuery query = new RuleInfoQuery();
            query.setIsAvailable(SystemCodeConstant.IS_ACTIVE.YES);
            query.setId(reuleId);
            List<RuleInfo> list = ruleInfoCustomMapper.queryRuleInfo(query);
            if(null == list || list.size() <= 0){
                return new ResultModel(ResultCode.ERROR.getCode(),"当前没有使用中的规则" ,null);
            }
            String content = list.get(0).getRuleContent().replaceAll("\n","\r\n");
            KieHelper helper = new KieHelper();
            helper.addContent(content, ResourceType.DRL);//将规则内容,写入临时文件drl中
            kieBase =  helper.build();//build之后删除临时文件drl
            kieBaseRedis.set(CommonConstants.DROOLS_KIE_BASE + reuleId , kieBase);

        }
        KieSession kieSession = kieBase.newKieSession();
        TargetSql sql = new TargetSql();
      //插入订单对象,给$messageOrder.getBusinessId() $messageOrder.getProvinceId()进行赋值
        kieSession.insert(messageOrder);
        kieSession.insert(sql);
        kieSession.fireAllRules();
    //整合之后会得到sql.getContent() sql的list
        kieSession.dispose();

        logger.info("sql ---------------" + sql.getContent());
        List<ResourceInfo> resourceInfos = resourceInfoCustomMapper.getResourceInfoForDrools(sql);

        messageOrder = oprateOrderUtil.updateOrderInfo(resourceInfos , messageOrder.getId());
        logger.info("conformRuleResource ---------------" + messageOrder.getConformRulesResource() );

        insertMessageOrderLog(messageOrder.getId(),messageOrder.getOrderNo(), SystemCodeConstant.ORDER_OPERATOR_TYPE.ALLOCATION_LAYWER,null, SystemCodeConstant.ORDER_OPERATOR_PERSON_TYPE.DROOLS,null, LogChanged.changed(befor,messageOrder),productId);
        return new ResultModel(ResultCode.SUCCESS.getCode(),"分配律师成功");
 <select id="getResourceInfoForDrools" resultMap="BaseResultMap">
    SELECT
    rio.* , rio.business_type_ids
    from(
    SELECT
    a.*, group_concat(b.business_type_id) AS business_type_ids
    FROM
    resource_info a
    LEFT JOIN resource_business_rel b ON a.id = b.resource_info_id
    GROUP BY
    a.id
    ) rio
    <include refid="QUERY_FOR_DROOLS"></include>
    GROUP BY
    rio.id
  </select>
   
  <sql id="QUERY_FOR_DROOLS">
    <where>
      rio.is_delete = 0
      <if test="data.content != null">
           <!--遍历sql条件-->
        <foreach collection="data.content" item="item" index="index"
                 open="AND (" close=")" separator=" AND ">
          ${item}
        </foreach>
      </if>
    </where>
  </sql>

其实规则引擎执行的核心是保存规则内容的规则文件,这个文件有很多格式,这边使用的drl格式。

 public static final ResourceType DRL = addResourceTypeToRegistry("DRL", "Drools Rule Language", "src/main/resources", "drl");
    public static final ResourceType GDRL = addResourceTypeToRegistry("GDRL", "Drools Rule Language", "src/main/resources", "gdrl");
    public static final ResourceType RDRL = addResourceTypeToRegistry("RDRL", "Drools Rule Language", "src/main/resources", "rdrl");
    public static final ResourceType XDRL = addResourceTypeToRegistry("XDRL", "Drools XML Rule Language", "src/main/resources", "xdrl");
    public static final ResourceType DSL = addResourceTypeToRegistry("DSL", "Drools DSL", "src/main/resources", "dsl");
    public static final ResourceType DSLR = addResourceTypeToRegistry("DSLR", "Drools DSL Rule", "src/main/resources", "dslr");
    public static final ResourceType RDSLR = addResourceTypeToRegistry("RDSLR", "Drools DSL Rule", "src/main/resources", "rdslr");
    public static final ResourceType DRF = addResourceTypeToRegistry("DRF", "Drools Rule Flow Language", "src/main/resources", "rf");
    public static final ResourceType BPMN2 = addResourceTypeToRegistry("BPMN2", "jBPM BPMN2 Language", "src/main/resources", "bpmn", "bpmn2");
    public static final ResourceType CMMN = addResourceTypeToRegistry("CMMN", "jBPM CMMN Language", "src/main/resources", "cmmn");
    public static final ResourceType DTABLE = addResourceTypeToRegistry("DTABLE", "Decision Table", "src/main/resources", "xls", "xlsx", "csv");
    public static final ResourceType PKG = addResourceTypeToRegistry("PKG", "Binary Package", "src/main/resources", "pkg");
    public static final ResourceType BRL = addResourceTypeToRegistry("BRL", "Drools Business Rule Language", "src/main/resources", "brl");
    public static final ResourceType CHANGE_SET = addResourceTypeToRegistry("CHANGE_SET", "Change Set", "src/main/resources", "xcs");
    public static final ResourceType XSD = addResourceTypeToRegistry("XSD", "XSD", "src/main/resources", "xsd");
    public static final ResourceType PMML = addResourceTypeToRegistry("PMML", "Predictive Model Markup Language", "src/main/resources", "pmml");
    public static final ResourceType DESCR = addResourceTypeToRegistry("DESCR", "Knowledge Descriptor", "src/main/resources", "descr");
    public static final ResourceType JAVA = addResourceTypeToRegistry("JAVA", "Java class", "src/main/java", "java");
    public static final ResourceType PROPERTIES = addResourceTypeToRegistry("PROPERTIES", "Properties file", "src/main/resources", "properties");
    public static final ResourceType SCARD = addResourceTypeToRegistry("SCARD", "Score Crd", "src/main/resources", "sxls");
    public static final ResourceType BAYES = addResourceTypeToRegistry("Bayes", "Bayesian Belief Network", "src/main/resources", "xmlbif", "bif");
    public static final ResourceType TDRL = addResourceTypeToRegistry("TDRL", "Drools Rule Language", "src/main/resources", "tdrl");
    public static final ResourceType TEMPLATE = addResourceTypeToRegistry("TEMPLATE", "Drools Rule Template", "src/main/resources", "template");
    public static final ResourceType DRT = addResourceTypeToRegistry("DRT", "Drools Rule Template", "src/main/resources", "drt");
    public static final ResourceType GDST = addResourceTypeToRegistry("GDST", "Guided Decision Table", "src/main/resources", "gdst");
    public static final ResourceType SCGD = addResourceTypeToRegistry("SCGD", "Guided Score Card", "src/main/resources", "scgd");
    public static final ResourceType SOLVER = addResourceTypeToRegistry("SOLVER", "OptaPlanner Solver Configuration", "src/main/resources", "solver");
    public static final ResourceType DMN = addResourceTypeToRegistry("DMN", "Decision Model and Notation", "src/main/resources", "dmn");
    public static final ResourceType FEEL = addResourceTypeToRegistry("FEEL", "Friendly Enough Expression Language", "src/main/resources", "feel");

其他格式文件的功能可以去了解,上面是jar包中的文件格式内容。

相关标签: drools