瘋狂Activiti6.0連載(18) Activiti與Drools整合

 本文節選自《瘋狂工作流講義(第2版)》

京東購買地址:https://item.jd.com/12246565.html

工作流Activiti6電子書http://blog.csdn.net/boxiong86/article/details/78488562

工作流Activiti6教學視頻http://blog.csdn.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


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.csdn.net/boxiong86/article/details/78488562

工作流Activiti6教學視頻http://blog.csdn.net/boxiong86/article/details/78608585

本書代碼共享地址:https://gitee.com/yangenxiong/CrazyActiviti

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章