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

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

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

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

Activiti与Drools整合

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

业务规则任务详解

        在调用规则前,需要告诉规则引擎加载哪些规则文件,而对于Activiti来说,这些文件都会被看作资源(数据被保存在ACT_GE_BYTEARRAY表中),因此在部署流程资源文件时,就需要提供这些规则文件。当执行流到达业务规则任务时,就会执行业务规则任务的行为,Activiti中对应的行为实现类是BusinessRuleTaskActivityBehavior,那么根据本章前面几节中Drools的API可以知道,这个类的实现应该是创建(获取缓存中的)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 = new Person("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,然后根据任务节点的配置,解析为事实实例,调用StatefulKnowledgeSession的insert方法插入到Working Memory中,最后会触发全部的规则并关闭资源。需要注意的是,触发规则时,会读取任务所配置的规则来添加一个规则拦截器,调用StatefulKnowledgeSession的fireAllRules(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 Sale implements Serializable {

    // 销售单号
    private String saleCode;
    // 销售日期
    private Date date;
    // 销售明细
    private List<SaleItem> items;    
    //折扣
    private BigDecimal discount = 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;
    }
    ...省略setter和getter方法
}
// 销售明细
public class SaleItem implements 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;
    }
    ...省略setter和getter方法
}

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

编写规则文件

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

        代码清单14-28:codes\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
    salience 1
    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
    salience 3
    when
        $s : Sale(getTotal() >= 200)
    then
        $s.setDiscount(new BigDecimal(0.7));
        update($s);
end

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

实现销售流程

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

图14-3 销售流程

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

        代码清单14-31:codes\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内容一致。本例中创建了4个Sale对象,代码清单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-32:

        codes\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打了七折。

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

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

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti

140509_5TSO_3665821.png

转载于:https://my.oschina.net/JavaLaw/blog/1578146

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值