规则引擎的应用
分享一个应用实例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包中的文件格式内容。