1. 程式人生 > >工作流引擎Activiti系列(一)——初識

工作流引擎Activiti系列(一)——初識

1、介紹

    幾乎任何一個公司的軟體開發都會涉及到流程,以往我們可能是這麼實現的:業務表新增標誌位標識流程的節點狀態,關聯批註表實現稽核意見,根據一些業務資料分析處理邏輯,分配任務到使用者,節點的排程,審批等.....這其實是很繁瑣的,且不說開發起來比較混亂,維護起來更是難上加難:

    

    Activiti剛好就能解決幾乎所有的這些問題,當流程開發變得簡單有趣。

    Activiti專案是一項新的基於Apache許可的開源BPM平臺,從基礎開始構建,旨在提供支援新的BPMN 2.0標準,包括支援物件管理組(OMG),面對新技術的機遇,諸如互操作性和雲架構,提供技術實現。

    作為開發者,使用Activiti帶給我的直接好處:

  1.     天然支援Spring(Spring需要在配置檔案中自己定義Activiti的Bean,Spring Boot則不需要)
  2.     流程定義通過畫流程圖實現(官方提供了相關工具,Eclipse也有外掛支援),簡單直觀,易維護,易修改。
  3.     審批條件引數化,流程分支簡單實現。
  4.     審批人可使用多種方式設定(支援使用者、使用者組、角色、候選組以及監聽器動態設定),靈活,簡單。
  5.     統一的審批介面,並不需要判斷流程當前節點和走向。
  6.     提供強大的JPA查詢,同時支援Name Query和Native Query。
  7.     流程資料與業務資料分離。

    後續文章會一步步介紹Activiti的功能,主要使用基於Spring Boot的工程,也會提供單純的Spring工程Demo。

2、示例

    此處演示一個小示例,暫不解釋程式碼,僅僅看看是怎樣用activiti實現的。

    場景就是請假,不過這裡稍稍多了點內容,就是請假由請假人對應部門的領導審批,而不是統一的某一部分人。

    2.1、使用Spring Boot工程

    首先建立Spring boot工程,為了演示方便,使用記憶體資料庫,完整pom檔案如下:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.anxpp</groupId>
	<artifactId>ActivitiDemo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>ActivitiDemo</name>
	<description>ActivitiDemo</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.3.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<!-- <dependency> -->
		<!-- <groupId>org.mybatis.spring.boot</groupId> -->
		<!-- <artifactId>mybatis-spring-boot-starter</artifactId> -->
		<!-- <version>1.1.1</version> -->
		<!-- </dependency> -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.activiti</groupId>
			<artifactId>spring-boot-starter-basic</artifactId>
			<version>5.17.0</version>
		</dependency>
		<!-- 記憶體資料庫  -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
		<!-- <dependency> -->
		<!-- <groupId>mysql</groupId> -->
		<!-- <artifactId>mysql-connector-java</artifactId> -->
		<!-- <scope>runtime</scope> -->
		<!-- </dependency> -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<distributionManagement>
		<repository>
			<id>Releases</id>
			<name>Nexus Release Repository</name>
			<url>http://anxpp.com/nexus/content/repositories/releases/</url>
		</repository>
		<snapshotRepository>
			<id>Snapshots</id>
			<name>Nexus Snapshot Repository</name>
			<url>http://anxpp.com/nexus/content/repositories/snapshots/</url>
		</snapshotRepository>
	</distributionManagement>
	<repositories>
		<repository>
			<id>nexus</id>
			<name>Nexus</name>
			<url>http://anxpp.com/nexus/content/groups/public/</url>
			<layout>default</layout>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>
</project>

    2.2、流程定義

    這裡使用eclipse的activiti designer外掛,外掛安裝方法另見:Eclipse安裝Activiti Designer外掛

    如我所願,這裡以請假流程,使用流程設計器得到如下流程定義:

    流程定義

    這個流程相當簡單。下面是定義的xml檔案:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="myProcess" name="My process" isExecutable="true">
    <extensionElements>
      <activiti:executionListener event="end" class="com.anxpp.demo.activiti.simple.listener.SimpleProcessEndListener"></activiti:executionListener>
    </extensionElements>
    <startEvent id="startevent_simple" name="Start"></startEvent>
    <userTask id="usertask1" name="領導審批">
      <extensionElements>
        <activiti:taskListener event="create" class="com.anxpp.demo.activiti.simple.listener.LeaderCheckListener"></activiti:taskListener>
      </extensionElements>
    </userTask>
    <endEvent id="endevent_simple" name="End"></endEvent>
    <sequenceFlow id="flow_toCheck" sourceRef="startevent_simple" targetRef="usertask_leadercheck"></sequenceFlow>
    <sequenceFlow id="flow_toEnd" sourceRef="usertask_leadercheck" targetRef="endevent_simple"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="startevent_simple" id="BPMNShape_startevent_simple">
        <omgdc:Bounds height="35.0" width="35.0" x="170.0" y="290.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="290.0" y="280.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent_simple" id="BPMNShape_endevent_simple">
        <omgdc:Bounds height="35.0" width="35.0" x="500.0" y="290.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

    裡面包含一個使用者任務、一個流程監聽器(流程結束後回寫業務資料狀態)、一個任務監聽器(以為審批是由申請員工對應部門的領導稽核的,使用監聽器可以靈活的設定任務審批候選人)。

    2.3、監聽器

    監聽器分任務監聽器和流程監聽器。

    任務監聽器

/**
 * 領導稽核監聽器
 * @author anxpp.com
 * 2016年12月24日 下午12:10:01
 */
public class LeaderCheckListener implements TaskListener{
	private static final long serialVersionUID = 4285398130708457006L;
	private final static Logger log = LoggerFactory.getLogger(LeaderCheckListener.class);
	@Override
	public void notify(DelegateTask task) {
		log.info("領導稽核監聽器...");
		//設定任務處理候選人
		UserService userService = SpringUtil.getBean(UserService.class);
		List<String> leaders = userService.getSimpleCheckerByDept(Long.valueOf(task.getVariable("dept").toString()));
		log.info(leaders.toString());
		log.info(task.getVariable("dept").toString());
		task.addCandidateUsers(leaders);
	}
}

 流程監聽器(此處並無實際程式碼,只給寫法):

/**
 * 流程監聽器
 * @author anxpp.com
 * 2016年12月24日 下午12:33:58
 */
public class SimpleProcessEndListener implements ExecutionListener{
	private static final long serialVersionUID = 5212042435691138021L;
	private final static Logger log = LoggerFactory.getLogger(SimpleProcessEndListener.class);
	@Override
	public void notify(DelegateExecution arg0) throws Exception {
		log.info("流程結束監聽器...");
		//TODO 修改業務資料狀態
	}
}

    單元測試

package com.anxpp.demo.activiti;
import java.util.Iterator;
import java.util.List;
import org.activiti.engine.task.Task;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.anxpp.demo.activiti.core.entity.User;
import com.anxpp.demo.activiti.core.service.UserService;
import com.anxpp.demo.activiti.simple.Config.Constant;
import com.anxpp.demo.activiti.simple.core.entity.ApplySimple;
import com.anxpp.demo.activiti.simple.core.service.ApplySimpleService;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ActivitiDemoApplicationTests {
	private final static Logger log = LoggerFactory.getLogger(ActivitiDemoApplicationTests.class);
	private static Long DEPT_TINY_SOFTWARE_STUDIO = 1L;
	private static Long DEPT_OTHER = 2L;
	@Autowired
	UserService userService;
	@Autowired
	ApplySimpleService simpleService;
	/**
	 * 測試程式啟動
	 */
	@Test
	public void contextLoads() {
	}
	/**
	 * 測試資料庫操作
	 */
	@Test
	public void testCURD(){
		Long countUser = userService.countUser();
		User user = new User();
		user.setName("anxpp0");
		user.setDept(DEPT_TINY_SOFTWARE_STUDIO);
		userService.save(user);
		Assert.assertEquals(new Long(countUser+1), userService.countUser());
	}
	/**
	 * 測試簡單流程
	 */
	@Test
	public void testSimpleActiviti(){
		long startAt = System.currentTimeMillis();
		//新增申請使用者
		User user = new User();
		user.setName("anxpp");
		user.setDept(DEPT_TINY_SOFTWARE_STUDIO);
		user.setPosition(Constant.POSITION_GENERAL);
		userService.save(user);
		//新增稽核使用者
		User userLeader1 = new User();
		userLeader1.setName("anxpp1");
		userLeader1.setDept(DEPT_TINY_SOFTWARE_STUDIO);
		userLeader1.setPosition(Constant.POSITION_LEADER);
		userService.save(userLeader1);
		User userLeader2 = new User();
		userLeader2.setName("anxpp2");
		userLeader2.setDept(DEPT_OTHER);
		userLeader2.setPosition(Constant.POSITION_LEADER);
		userService.save(userLeader2);
		Long countHis = simpleService.countProcess();
		Long countLeader1Task = simpleService.countTask(userLeader1.getId());
		Long countLeader2Task = simpleService.countTask(userLeader2.getId());
		//建立請假申請
		ApplySimple applySimple = new ApplySimple();
		applySimple.setInsertBy(user.getId());
		applySimple.setComtent("有事請假...");
		//啟動請假流程
		simpleService.startProcess(applySimple);
		/**斷言歷史流程+1*/
		Assert.assertEquals(simpleService.countProcess(), new Long(countHis+1));
		/**斷言領導任務變化*/
		Assert.assertEquals(simpleService.countTask(userLeader1.getId()), new Long(countLeader1Task+1));
		Assert.assertEquals(countLeader2Task, simpleService.countTask(userLeader2.getId()));
		//獲取使用者任務
		List<Task> taskUserLeader1 = simpleService.getTaskByUid(userLeader1.getId());
		//處理任務
		Iterator<Task> iterator = taskUserLeader1.iterator();
		while(iterator.hasNext()){
			Task task = iterator.next();
			/**斷言任務節點名稱*/
			Assert.assertEquals("領導審批", task.getName());
			simpleService.completeSimpleCheck(task.getId(), ApplySimpleService.STATE_PASS);
		}
		/**斷言領導任務變化*/
		Assert.assertEquals(countLeader1Task, simpleService.countTask(userLeader1.getId()));
		Assert.assertEquals(countLeader2Task, simpleService.countTask(userLeader2.getId()));
		System.err.println("asdf");
		log.info("測試完成,花費時間:"+(System.currentTimeMillis()-startAt));
	}
}

    執行單元測試,OK!通過!