Activiti工作流引擎使用調研和一個Demo

Activiti調研以及雙人複合的Demo演示

1.說明

Activiti是一種業務流程管理(BPM)框架,它是覆蓋了業務流程管理、工作流、服務協作等領域的一個開源的、靈活的、易擴展的可執行流程語言框架。Activiti基於Apache許可的開源BPM平臺,它特色是提供了Eclipse插件,開發人員可以通過插件直接繪畫出業務,結合相應的業務處理代碼,能夠靈活handle各自複雜業務流程處理需求。

2.軟件環境準備

我們在Idea裏嘗試使用Activiti,但是官網並沒有Idea的插件,只有Eclispe平臺的。但是在plugin repo裏找到了ActiBPM, ActiBPM插件非常難用,也無法生成圖片,評論裏很多吐槽,而且在2014年因爲違反GPL已經停止維護。目前Activiti在Idea平臺並不好用,也就是將就用的程度,在你組織好結構以後,修改了審批人是沒法保存的,這個我看其他評論裏也遇到了,修改了自動化流程的Class路徑和表達式後卻無法保存。這個很無奈,考慮現場大家使用的工具是IDEA。我嘗試編輯好流程以後,用文本編輯器根據語法自己去做微調,這時候就不要再用插件再次打開bpmn文件了,因爲你再次保存,插件可能把你辛苦調整的參數給破壞。

  • 安裝插件
    安裝插件

  • maven

  • 拖拽設計流程

    這裏寫圖片描述
    因爲Plugin的bug,導致修改左側的參數無法保存。需要手工修改Bpmn文件。如下,重點關注下serviceTask這一項。

    <process id="cmpc_test" isClosed="false" isExecutable="true" processType="None">
        <startEvent id="_2" name="StartEvent"/>
        <userTask activiti:assignee="${userId}" activiti:exclusive="true" id="_3" name="提交雙人複合"/>
        <userTask activiti:assignee="複合管理員" activiti:exclusive="true" id="_5" name="審批"/>
        <endEvent id="_4" name="EndEvent"/>
        <sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
        <sequenceFlow id="_7" sourceRef="_3" targetRef="_5"/>
        <serviceTask activiti:delegateExpression="${serviceClassDelegateSample}" activiti:exclusive="true" id="_8" name="執行"/>
        <sequenceFlow id="_9" name="同意" sourceRef="_5" targetRef="_8">
          <conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='agree'}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="_10" sourceRef="_8" targetRef="_4"/>
        <sequenceFlow id="_11" name="拒絕" sourceRef="_5" targetRef="_4">
          <conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='deny'}]]></conditionExpression>
        </sequenceFlow>
      </process>

3. Demo展示

根據上邊的設計流程,準備了一份業務代碼,我貼出來maven配置,以及activiti的配置文件。

  1. maven 配置

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>test</groupId>
      <artifactId>test</artifactId>
      <packaging>war</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>test Maven Webapp</name>
      <url>http://maven.apache.org</url>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
        </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.6</version>
        </dependency>
        <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-engine</artifactId>
          <version>5.22.0</version>
        </dependency>
        <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-spring</artifactId>
          <version>5.22.0</version>
        </dependency>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.8-dmr</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>4.3.14.RELEASE</version>
          <scope>compile</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>4.3.14.RELEASE</version>
          <scope>compile</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version> 3.2.4.RELEASE  </version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
      <build>
        <finalName>test</finalName>
      </build>
    </project>
    

    這裏是常用的依賴。

  2. activiti 配置

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    
    
        <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
            <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="jdbcUrl" value="jdbc:mysql://18.217.123.86:3306/activiti?useUnicode=true&amp;characterEncoding=utf8"/>
            <property name="jdbcUsername" value="root"/>
            <property name="jdbcPassword" value="123456"/>
            <property name="databaseSchemaUpdate" value="true"/>
        </bean>
    
        <!--加載屬性文件-->
        <bean id="alaudaServiceSample" class="com.springdemo.controller.AlaudaServiceImpl">
        </bean>
    
        <bean id="serviceClassDelegateSample" class="com.springdemo.controller.ServiceClassDelegateSample">
            <property name="alaudaService" ref="alaudaServiceSample" />
        </bean>
    
        <!--<context:component-scan base-package="com.springdemo.controller" />-->
    </beans>
    

    配置自動掃描的情況下,spring無法注入自動任務中的Spring bean 對象,導致空指針。去官網查,目前只支持通過顯式聲明來做內部對象注入,給的用法也是顯式聲明,配置並不是和Spring配置文件放在一起,所以目前看,並不影響在中信的統一平臺3.0中去使用。

  3. Code

    package com.springdemo.controller;
    import org.activiti.engine.*;
    import org.activiti.engine.repository.Deployment;
    import org.activiti.engine.runtime.ProcessInstance;
    import org.activiti.engine.task.Task;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @RunWith(SpringJUnit4ClassRunner.class) //使用junit4進行測試
    @ContextConfiguration(locations={"classpath:applicationContext.xml"}) //加載配置文件
    public class ActivitiTest2 extends AbstractJUnit4SpringContextTests {
    
        @Test
        public void createActivitiEngine(){
            /**
             * 1. 通過ProcessEngines 來獲取默認的流程引擎
             */
            //  默認會加載類路徑下的 activiti.cfg.xml
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
            System.out.println("通過ProcessEngines 來獲取流程引擎");
    
            /**
             * 2. 部署流程
             */
            Deployment deployment = processEngine.getRepositoryService().createDeployment().name("雙人複合")
                    .addClasspathResource("diagrams/recheck.bpmn").deploy();
            System.out.println("部署的id "+deployment.getId());
            System.out.println("部署的名稱 "+deployment.getName());
    
            /**
             * 3. 準備數據
             */
            Map<String,Object> params=new HashMap<String, Object>();
            params.put("userId", "xialingming");
            ApplyInfoBean applyInfoBean = new ApplyInfoBean();
            applyInfoBean.setId(1);
            applyInfoBean.setCost(300);
            applyInfoBean.setDate(new Date());
            applyInfoBean.setAppayPerson("夏某某");
    
    
            /**
             * 4. 發起一個流程
             */
            //流程定義的key
            String processDefinitionKey = "cmpc_test";
            ProcessInstance pi = processEngine.getRuntimeService()
                    .startProcessInstanceByKey(processDefinitionKey, params);
            System.out.println("流程實例ID:"+pi.getId());//流程實例ID
            System.out.println("流程定義ID:"+pi.getProcessDefinitionId());//流程定義ID
    
            //把數據傳給任務
            TaskService taskService = processEngine.getTaskService();//與正在執行的任務管理相關的Service
            Task taskFirst = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
            taskService.setVariable(taskFirst.getId(), "applyInfoBean", applyInfoBean);
            String taskId = taskFirst.getId();
            //taskId:任務id
            processEngine.getTaskService().complete(taskId);
            System.out.println("當前任務執行完畢");
    
            String assignee2 = "複合管理員";
            List<Task> list2 = processEngine.getTaskService()//與正在執行的任務管理相關的Service
                    .createTaskQuery()//創建任務查詢對象
                    .taskAssignee(assignee2)//指定個人任務查詢,指定辦理人
                    .list();
            if(list2!=null && list2.size()>0){
                for(Task task2:list2){
                    ApplyInfoBean appayBillBean=(ApplyInfoBean) taskService.getVariable(task2.getId(), "applyInfoBean");
                    //通過夏某某的流程
                    if(appayBillBean != null && appayBillBean.getAppayPerson().equals("夏某某")) {
                        Map<String, Object> variables = new HashMap<String, Object>();
                        variables.put("result", "agree");
                        //與正在執行的任務管理相關的Service
                        processEngine.getTaskService().complete(task2.getId(), variables);
                        System.out.println("完成任務:任務ID:"+task2.getId());
                    }
                }
            }
        }
    
    }

    自動觸發的任務是如何實現的:

    package com.springdemo.controller;
    
    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.JavaDelegate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    
    
    import java.io.Serializable;
    import java.util.logging.Logger;
    @Service
    public class ServiceClassDelegateSample implements JavaDelegate,Serializable {
    
        private static AlaudaService alaudaService;
    
        private final Logger log = Logger.getLogger(ServiceClassDelegateSample.class.getName());
    
        public void execute(DelegateExecution execution) throws Exception {
            log.info(alaudaService.sayHello("xialingming"));
            Thread.sleep(10000);
            execution.setVariable("task1", "I am task 1");
            log.info("I am task 1.");
        }
    
        public void setAlaudaService(AlaudaServiceImpl alaudaService) {
            this.alaudaService = alaudaService;
        }
    }

4. 執行結果演示:

運行流程後:
這裏寫圖片描述

以上是我在自己電腦上適用了activiti。總體來說比較輕量,和Spring結合也還好,不是很自動化(也可能是還沒找到更酷的方法)。走了workflow以後,代碼架構會更清晰一些,但是也帶來了一些不夠靈活的地方,比如之前的值班對接雙人複合的修改,可能要重新設計,改造;雙人複合過於簡單,是否值得去使用一個相對複雜的工作流引擎,也是值得考慮的事。

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