疯狂Activiti6.0连载(18) Activiti与Drools整合

 本文节选自《疯狂工作流讲义(第2版)》

京东购买地址:https://item.jd.com/12246565.html

工作流Activiti6电子书http://blog.****.net/boxiong86/article/details/78488562

工作流Activiti6教学视频http://blog.****.net/boxiong86/article/details/78608585

ActivitiDrools整合

使用Activiti中的业务规则任务(Business Rule Task可以执行一个或者多个业务规则,当前Activiti只支持Drools根据流程任务章节可知,每个流程活动都会有自己的行为,那么Activiti在实例业务规则任务行为的时候,只需要使用DroolsAPI,就可以实现规则文件的加载、事实实例的插入和规则触发等操作,任务的定义者只需要提供参数、规则和计算结果等信息,就可以在Activiti中调用规则。

业务规则任务详解

在调用规则前,需要告诉规则引擎加载哪些规则文件,而对于Activiti来说,这些文件都会被看作资源(数据被保存在ACT_GE_BYTEARRAY表中),因此在部署流程资源文件时,就需要提供这些规则文件。当执行流到达业务规则任务时,就会执行业务规则任务的行为,Activiti中对应的行为实现类是BusinessRuleTaskActivityBehavior,那么根据本章前面几节中DroolsAPI可以知道,这个类的实现应该是创建(获取缓存中的)KnowledgeBase实例,然后创建一个StatefulKnowledgeSession实例,插入事实实例,最后调用fireAllRules方法触发规则。BusinessRuleTaskActivityBehavior的实现大致如代码清单14-26所示。

代码清单14-26

//创建一个KnowledgeBuilder

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory

.newKnowledgeBuilder();

//添加规则资源到KnowledgeBuilder

kbuilder.add(ResourceFactory.newClassPathResource("rule/MyDrools.drl",

FirstTest.class), ResourceType.DRL);

if (kbuilder.hasErrors()) {

System.out.println(kbuilder.getErrors().toString());

System.exit(0);

}

//获取知识包集合

Collection<KnowledgePackage> pkgs = kbuilder

.getKnowledgePackages();

//创建KnowledgeBase实例

KnowledgeBase kbase =kbuilder.newKnowledgeBase();

//将知识包部署到KnowledgeBase

kbase.addKnowledgePackages(pkgs);

//使用KnowledgeBase创建StatefulKnowledgeSession

StatefulKnowledgeSession ksession = kbase

.newStatefulKnowledgeSession();

//创建事实

Person p1 = newPerson("person 1", 11);

//插入到Working Memory

ksession.insert(p1);

//匹配规则

ksession.fireAllRules();

//关闭当前session的资源

ksession.dispose();

从代码清单14-26开始,将会是BusinessRuleTaskActivityBehavior做的工作,Activiti的实现与代码清单14-26存在差异KnowledgeBase实例的创建将由Activiti的其他类完成,包括KnowledgeBuilder的创建、编译信息输出等工作,BusinessRuleTaskActivityBehavior的实现中,得到KnowledgeBase后,会创建一个StatefulKnowledgeSession,然后根据任务节点的配置,解析为事实实例,调用StatefulKnowledgeSessioninsert方法插入到Working Memory,最后会触发全部的规则并关闭资源。需要注意的是,触发规则时,会读取任务所配置的规则来添加一个规则拦截器调用StatefulKnowledgeSessionfireAllRules(AgendaFilter filte)方法来触发规则,如果在任务中没有配置使用(或者不使用)的规则,那么将调用无参数的fireAllRules方法。在接下来的两个小节,将以一个销售流程为基础,在Activiti中调用规则。

制定销售单优惠规则

假设当前有一个销售流程,销售人员在录入销售商品后,系统需要对录入的商品进行规则处理,例如在单笔消费100元以上打九折、200元以上打八折等优惠策略,都可以在规则文件中定义,然后通过业务规则任务调用,最后通过一个ServiceTask来输出计算后的结果。在设定销售流程前,可以先设计相应的销售对象。代码清单14-27为一个销售单对象和一个销售单明细对象。

代码清单14-27

codes\14\14.7\drools-sale\src\org\crazyit\activiti\Sale.java

codes\14\14.7\drools-sale\src\org\crazyit\activiti\SaleItem.java

// 销售单对象

public class Saleimplements Serializable {

 

//销售单号

private String saleCode;

//销售日期

private Date date;

//销售明细

private List<SaleItem> items;

//折扣

private BigDecimaldiscount = new BigDecimal(1);

public Sale(String saleCode, Date date) {

super();

this.saleCode = saleCode;

this.date = date;

this.items = new ArrayList<SaleItem>();

}

//返回日期为星期几

public int getDayOfWeek() {

Calendar c = Calendar.getInstance();

c.setTime(this.date);

int dow = c.get(Calendar.DAY_OF_WEEK);

return dow;

}

//返回该销售单的总金额(优惠前)

public BigDecimal getTotal() {

BigDecimal total = new BigDecimal(0);

for (SaleItem item : this.items) {

BigDecimal itemTotal = item.getPrice().multiply(item.getAmount());

total = total.add(itemTotal);

}

total = total.setScale(2, BigDecimal.ROUND_HALF_UP);

return total;

}

//返回优惠后的总金额

public BigDecimal getDiscountTotal() {

BigDecimal total = getTotal();

total = total.multiply(this.discount).setScale(2, BigDecimal.ROUND_HALF_UP);

return total;

}

public void setDiscount(BigDecimal dicsount) {

this.discount = dicsount.setScale(2, BigDecimal.ROUND_HALF_UP);

}

public BigDecimal getDiscount() {

return this.discount;

}

...省略settergetter方法

}

// 销售明细

public class SaleItemimplements Serializable {

 

//商品名称

private String goodsName;

//商品单价

private BigDecimal price;

//数量

private BigDecimal amount;

 

public SaleItem(String goodsName, BigDecimal price, BigDecimal amount) {

super();

this.goodsName = goodsName;

this.price = price;

this.amount = amount;

}

...省略settergetter方法

}

代码清单14-27中的Sale对象,表示在销售过程中产生的一笔交易,一张销售单中有多个销售明细,每个明细表示所销售的商品信息,包括商品名称、单价和数量。在代码清单14-27中,Sale对象提供了getDayOfWeekgetTotal方法,用于返回销售单日期是星期几和销售单总金额,这两个方法将会被规则的条件所调用,判断是否符合规则触发的条件Sale对象中的getDiscountTotal方法,用于返回优惠后销售单的总金额,这个方法将会用于显示结果值。销售单中有一个discount的属性,用标识销售单的打折情况

编写规则文件

设计完事实对象后,就可以制定各种销售规则,只需要按照具体的业务和Drools的语法来制定规则。假设需要满足以下的销售规则:每周六和周日,全部商品打九折;消费满100打八折,满200打七折。根据该业务,设定的Drools规则如代码清单14-28所示。

代码清单14-28codes\14\14.7\drools-sale\resource\rule\Sale.drl

package org.crazyit.activiti;

 

import java.util.*;

import java.math.*;

 

// 周六周日打九折

rule "Sat. and Sun. 90%"

no-loop true

lock-on-active true

salience1

when

$s : Sale(getDayOfWeek() == 1 || getDayOfWeek() == 7)

then

$s.setDiscount(new BigDecimal(0.9));

update($s);

end

 

// 100元打八折

rule "100 80%"

no-loop true

lock-on-active true

salience  2

when

$s : Sale(getTotal() >= 100)

then

$s.setDiscount(new BigDecimal(0.8));

update($s);

end

 

// 200元打七折

rule "200 70%"

no-loop true

lock-on-active true

salience3

when

$s : Sale(getTotal() >= 200)

then

$s.setDiscount(new BigDecimal(0.7));

update($s);

end

代码清单14-28中定义了三个规则,这三个规则都设置了no-looplock-on-active属性为true表示一个规则被触发后,其他规则(包括自身)将不会被再次触发,三个规则中设置了规则的优先级,200元打七折的优先级最高,周六周日打九折的规则优先级最低,如果一笔销售发生在周六日,同时也满200元的话,这时会触发“200元打七折”的业务规则代码清单中的三个规则,符合条件后,均会调用SalesetDiscount方法设置销售单的折扣属性。

实现销售流程

制定了销售规则后,就可以在Activiti中设计销售流程,本例的销售流程较为简单,在销售员录入销售数据后(使用User Task),将数据交给业务规则任务(Business Rule Task)进行处理,最后使用一个简单的Service Task进行输出,流程结束,当然,在实际应用的过程中,会有更复杂的后续流程,但并不是本例的重点。本例设计的销售流程如图14-3所示,对应的流程文件内容为代码清单14-28

疯狂Activiti6.0连载(18) Activiti与Drools整合

14-3销售流程

代码清单14-30codes\14\14.7\drools-sale\resource\bpmn\SaleRule.bpmn

<process id="process1" name="process1">

<startEvent id="startevent1" name="Start"></startEvent>

<businessRuleTask id="businessruletask1" name="进行优惠策略应用"

activiti:ruleVariablesInput="${sale1}, ${sale2}, ${sale3}, ${sale4}"

activiti:resultVariable="saleResults"></businessRuleTask>

省略其他元素

</process>

代码清单14-30粗体字代码,使用了businessRuleTask该任务中会以四个流程参数(sale1sale4)作为规则事实,交给规则引擎进行处理,最终返回结果的名称为“saleResults”,结果类型是一个集合。本例中的四个Sale流程参数,为代码清单14-25中的Sale对象,需要匹配的规则为代码清单14-26的规则(周六日九折、100元以上八折、200元以上七折)。为了让流程引擎能加载规则文件(drl),需要在资源部署时将规则文件一并部署到流程引擎中,流程的部署以及运行,如代码清单14-31所示。

代码清单14-31codes\14\14.7\drools-sale\src\org\crazyit\activiti\SaleProcess.java

public static void main(String[] args) {

//创建流程引擎

ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

//得到流程存储服务组件

RepositoryService repositoryService = engine.getRepositoryService();

//得到运行时服务组件

RuntimeService runtimeService = engine.getRuntimeService();

//得到任务服务组件

TaskService taskService = engine.getTaskService();

//部署流程文件

repositoryService.createDeployment()

.addClasspathResource("rule/Sale.drl")

.addClasspathResource("bpmn/SaleRule.bpmn").deploy();

ProcessInstance pi = runtimeService

.startProcessInstanceByKey("process1");

//创建事实实例,符合周六日打九折条件

Sale s1 = new Sale("001", createDate("2017-07-01"));

SaleItem s1Item1 = new SaleItem("矿泉水", new BigDecimal(5),

new BigDecimal(4));

s1.addItem(s1Item1);

//100打八折

Sale s2 = new Sale("002", createDate("2017-07-03"));

SaleItem s2Item1 = new SaleItem("爆米花", new BigDecimal(20),

new BigDecimal(5));

s2.addItem(s2Item1);

//200打七折

Sale s3 = new Sale("003", createDate("2017-07-03"));

SaleItem s3Item1 = new SaleItem("可乐一箱", new BigDecimal(70), new BigDecimal(3));

s3.addItem(s3Item1);

//星期天满200

Sale s4 = new Sale("004", createDate("2017-07-02"));

SaleItem s4Item1 = new SaleItem("爆米花一箱", new BigDecimal(80), new BigDecimal(3));

s4.addItem(s4Item1);

Map<String, Object> vars = new HashMap<String, Object>();

vars.put("sale1", s1);

vars.put("sale2", s2);

vars.put("sale3", s3);

vars.put("sale4", s4);

//查找任务

Task task = taskService.createTaskQuery().processInstanceId(pi.getId())

.singleResult();

taskService.complete(task.getId(), vars);

}

 

static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

 

//根据字符串创建日期对象

static Date createDate(String date) {

try {

return sdf.parse(date);

} catch (Exception e) {

throw new RuntimeException("parse date error: " + e.getMessage());

}

}

 

代码清单14-31中的粗体字代码,除了正常部署流程文件(.bpmn)外,还将一份Sale.drl部署到流程引擎中,该份文件内容与代码清单14-26内容一致。本例中创建了4Sale对象,代码清单14-31中的创建了第一个销售单实例,该实例将会满足周六日打九折的条件创建的Sale对象,总金额等于100元,符合满100元打八折的条件创建的Sale对象,总金额为210元,符合满200打七折的条件。创建的Sale对象,总金额为240元,并且发生在周日,即同时满足两个规则的条件,但是根据代码清单14-26中的规则,200元打七折的规则比周六日打九折的规则优先级高,因此可以知道,第四个Sale对象只会触发“200元打七折”的规则。如果需要成功运行代码清单14-31需要配置activiti.cfg.xml,为其加入规则文件的部署实现类,本例中activiti.cfg.xml的配置如下:

<bean id="processEngineConfiguration"

class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">

省略其他元素

<property name="customPostDeployers">

<list>

<bean class="org.activiti.engine.impl.rules.RulesDeployer" />

</list>

</property>

</bean>

以上配置的粗体部分为新加入的规则部署者。在整个销售流程中,当业务规则任务完成后,执行流会到达一个Service Task,在本例中,这个Service Task仅仅用于将规则处理后的销售单结果输出,Service Task的实现如代码清单14-32所示。

代码清单14-32codes\14\14.7\drools-sale\src\org\crazyit\activiti\SaleJavaDelegate.java

public class SaleJavaDelegate implements JavaDelegate {

 

public void execute(DelegateExecution execution) {

Collection sales = (Collection) execution.getVariable("saleResults");

System.out.println("输出处理结果:");

for (Object obj : sales) {

Sale sale = (Sale) obj;

System.out.println("销售单:" + sale.getSaleCode() + " 原价:"

+ sale.getTotal() + " 优惠后:" + sale.getDiscountTotal()

+ " 折扣:" + sale.getDiscount());

}

}

}

在流程最后的Service Task中,得到业务规则任务处理后的结果(一个集合),然后对集合进行遍历,强制类型转换为Sale对象,然后将Sale的各个信息输出。运行代码清单14-31,最终输出如下:

输出处理结果:

销售单:002原价:100.00优惠后:80.00折扣:0.80

销售单:001原价:20.00优惠后:18.00折扣:0.90

销售单:004原价:240.00优惠后:168.00折扣:0.70

销售单:003原价:210.00优惠后:147.00折扣:0.70

根据结果可知,相应的Sale对象均按预期匹配到不同的规则,销售单001打了九折,销售单002打了八折,销售单003打了七折,销售单004打了七折。

京东购买地址:https://item.jd.com/12246565.html

工作流Activiti6电子书http://blog.****.net/boxiong86/article/details/78488562

工作流Activiti6教学视频http://blog.****.net/boxiong86/article/details/78608585

本书代码共享地址:https://gitee.com/yangenxiong/CrazyActiviti

疯狂Activiti6.0连载(18) Activiti与Drools整合

疯狂Activiti6.0连载(18) Activiti与Drools整合