1、介绍

    几乎任何一个公司的软件开发都会涉及到流程,以往我们可能是这么实现的:业务表添加标志位标识流程的节点状态,关联批注表实现审核意见,根据一些业务数据分析处理逻辑,分配任务到用户,节点的调度,审批等.....这其实是很繁琐的,且不说开发起来比较混乱,维护起来更是难上加难:

    

    Activiti刚好就能解决几乎所有的这些问题,当流程开发变得简单有趣。

    官网:https://www.activiti.org/

    官方文档:https://www.activiti.org/userguide/

    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!通过!

    完整实例源码地址:https://github.com/anxpp/activitiSimpleDemo.git

    如果不想用git,也可以直接下载源码:http://download.csdn.net/detail/anxpp/9725907

    activiti designer插件zip安装包:http://download.csdn.net/detail/anxpp/9725902

    带activiti designer插件基于JEE的eclipse4.6绿色压缩包:http://download.csdn.net/detail/anxpp/9725904