• 今天内容安排:
    • 1、工作流概念
    • 2、安装流程设计器插件(是eclipse插件)--> 可以设计流程图
    • 3、了解activiti 框架目录结构
    • 4、创建activiti 数据库(共23张表)
    • 5、使用activiti 的API操作流程
    • 6、总结activiti 的API

1、工作流概念

  • 工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。
  • 工作流管理系统(Workflow Management System, WfMS)是一个软件系统,它完成工作量的定义和管理,并按照在系统中预先定义好的工作流逻辑进行工作流实例的执行。工作流管理系统不是企业的业务系统,而是为企业的业务系统的运行提供了一个软件的支撑环境。
  • 工作流管理联盟(WfMC,Workflow Management Coalition)给出的关于工作流管理系统的定义是:工作流管理系统是一个软件系统,它通过执行经过计算的流程定义去支持一批专门设定的业务流程。工作流管理系统被用来定义、管理、和执行工作流程。
  • 工作流管理系统的目标:管理工作的流程以确保工作在正确的时间被期望的人员所执行--在自动化进行的业务过程中插入人工的执行和干预。

2、安装流程设计器插件(是eclipse插件)--> 可以设计流程图

方式一:
  第一步:获得activiti-eclipse-plugin.zip文件
  第二步:将zip文件解压到eclipse的dropins目录中

day57_BOS项目_09-LMLPHP
  第三步:重启eclipse,打开Window --> Preferences --> Activiti -- > Save --> 勾选 Export marshallers 选项
  方式一不一定能安装成功,因为activiti版本可能与eclipse的版本不符。
方式二:
  第一步:点击eclipse上方工具栏的Help,选择 Install New Software… --> Add..
  第二步:填写插件名称和安装地址
    Name: Activiti BPMN 2.0 designer
    Location: http://activiti.org/designer/update/
    之后点击OK
day57_BOS项目_09-LMLPHP
    之后点击Next --> I accept the terms of the license agreements --> Finish,稍等几分钟,等待安装。
  第三步:安装完成后,我们在new的时候,操作面板中便有activiti的相关文件了。
  方式二也有可能不成功,我们这里推荐方式三。
方式三:
  第一步:先下载好对应的离线包,可以是jar或者zip格式的,下载地址为:http://www.activiti.org/designer/archived/activiti-designer-5.18.0.zip
  第二步:然后还是在下述对话框中,只是不再输入url,而是改为选择已经下来的zip离线包的路径
day57_BOS项目_09-LMLPHP
  第三步:然后下一步下一步安装即可。
  注意:由于我在第一步中已经使用在线的方式安装过,所以即使我这里选择了离线包,后续安装的时候eclipse还是会尝试从网络上去下载,所以需要在Install New SoftWare 对话框中选择 "Avaible Software Suits"或 "Manage..",在弹出的对话框中选中刚才http路径的那个资源,把他删了,然后再来使用离线方式安装,此时才会真正使用离线安装包。
  第四步:重启eclipse,打开Window --> Preferences --> Activiti -- > Save --> 勾选 Export marshallers 选项
day57_BOS项目_09-LMLPHP
  第五步:使用安装好的插件设计流程图,点击新建工程New --> Other… 打开面板,如果看到下图内容:
day57_BOS项目_09-LMLPHP
演示使用效果:
day57_BOS项目_09-LMLPHP

3、了解activiti 框架目录结构

day57_BOS项目_09-LMLPHP
  • 工作流框架底层需要有数据库支持。
  • activiti5.13版本对应23张表,activiti框架底层使用mybatis操作数据库。
  • JBPM4.4框架底层对应18张表,底层使用hibernate操作数据库。
  • BPMN业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)。

4、创建activiti 数据库(共23张表)

4.1、执行框架提供的sql文件建表(建议)

day57_BOS项目_09-LMLPHP
23张表如下:
day57_BOS项目_09-LMLPHP
23张表的命名详解:
Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。 `用途也和服务的API对应`。
    1) ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源,比如:图片、规则等等。
    2) ACT_RU_*: 'RU'表示runtime。 这些表示运行时的表,包含流程实例、任务、变量、异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
    3) ACT_ID_*: 'ID'表示identity。 这些表包含身份信息,比如:用户、组等等。
    4) ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如:历史流程实例、变量、任务等等。
    5) ACT_GE_*: 通用数据, 用于不同场景下。

4.2、使用框架提供的自动建表方式(不建议)

第一步:创建普通Java项目并导入jar包
第二步:提供配置文件activiti-context.xml,进行相应的配置

<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.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti_day01"/>
        <property name="jdbcUsername" value="root"/>
        <property name="jdbcPassword" value="root"/>
        <property name="databaseSchemaUpdate" value="true"/><!-- 自动建表 -->
    </bean>
    <!-- 使用工厂创建流程引擎对象 -->
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
        <property name="processEngineConfiguration" ref="processEngineConfiguration"></property>
    </bean> 
</beans>

第三步:创建流程引擎对象,自动建表(2种方式)

package com.itheima.activiti;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;

public class HelloWorld {
    /**
     * 使用框架提供的自动建表方式创建23张表
     */

    @Test
    public void test1() {
        String resource = "activiti-context.xml"// 配置文件
        String beanName = "processEngineConfiguration";
        // 读取配置文件,获得流程引擎对象
        ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(resource, beanName);
        ProcessEngine processEngine = config.buildProcessEngine();
    }

    /**
     * 使用默认配置的要求:
     *      1、配置文件必须在classpath根目录下
     *      2、配置文件名称必须为activiti-context.xml或者activiti.cfg.xml
     *      3、配置文件中配置对象的id必须为processEngineConfiguration 
     *      4、工厂对象的id必须为processEngine
     */

    @Test
    public void test2() {
        // 获得默认的流程引擎对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    }
}

5、使用activiti 的API操作流程

使用插件设计一个流程图:

day57_BOS项目_09-LMLPHP

5.1、部署流程定义

  • 即就是将流程定义规则保存到数据库。
    示例代码如下:
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    /**
     * 部署 流程定义,一共操作了3张数据表:
     *      act_re_deployment(部署表)
     *      act_re_procdef(流程定义表)
     *      act_ge_bytearray(二进制表)
     */

    @Test
    public void test3() 
{
        // 创建一个部署构建器对象
        DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
        // 读取流程定义文件(bpmn、png)
        deploymentBuilder.addClasspathResource("qingjialiucheng.bpmn");
        deploymentBuilder.addClasspathResource("qingjialiucheng.png");
        // 部署流程定义
        Deployment deployment = deploymentBuilder.deploy();
        System.out.println(deployment.getId());
    }

5.2、查询流程定义

    /**
     * 查询 流程定义,activiti框架一共操作了1张数据库表:
     *      act_re_procdef(流程定义表)
     */

    @Test
    public void test4() 
{
        // 创建流程定义查询对象
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition pd : list) {
            System.out.println(" id = " + pd.getId() + 
                               " name = " + pd.getName() + 
                               " version = " + pd.getVersion() + 
                               " key = " + pd.getKey());
        }
    }

5.3、根据流程定义启动流程实例

  • 流程实例:根据某个流程定义一次具体的执行过程就是一个流程实例。流程定义和流程实例的关系是一对多。
    /**
     * 根据流程定义启动流程实例,activiti框架一共操作了2张数据库表:
     *      act_ru_execution(流程实例表)
     *      act_ru_task(任务表)
     */

    @Test
    public void test5() 
{
        String processDefinitionId = "qingjialiucheng:2:104"// 流程定义id
        // 创建流程实例对象
        ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceById(processDefinitionId);
        System.out.println(processInstance.getId());
    }

5.4、查询个人任务

    /**
     * 查询 个人任务,activiti框架一共操作了1张数据库表:
     *      act_ru_task(任务表)
     */

    @Test
    public void test6() 
{
        // 创建任务查询对象
        TaskQuery query = processEngine.getTaskService().createTaskQuery();
        query.taskAssignee("王五"); // 根据办理人过滤
        query.orderByTaskCreateTime().desc(); // 根据任务创建时间排序,降序
        List<Task> list = query.listPage(010);
        for (Task task : list) {
            System.out.println(" id = " + task.getId() + 
                               " name = " + task.getName() + 
                               " processInstanceId = " + task.getProcessInstanceId());
        }
    }

5.5、办理个人任务

    /**
     * 办理 个人任务,activiti框架一共操作了2张数据库表:
     *      act_ru_execution(流程实例表)
     *      act_ru_task(任务表)
     */

    @Test
    public void test7() 
{
        String taskId = "304"// 任务id
        processEngine.getTaskService().complete(taskId);
    }

6、总结activiti 的API

  • 几个接口(和表有对应关系):

    • Deployment --> act_re_deployment(部署表)
    • ProcessDefinition --> act_re_procdef(流程定义表)
    • ProcessInstance --> act_ru_execution(流程实例表)
    • Task --> act_ru_task(任务表)
  • 几个Query对象

    • DeploymentQuery --> act_re_deployment
    • ProcessDefinitionQuery --> act_re_procdef
    • ProcessInstanceQuery --> act_ru_execution
    • TaskQuery --> act_ru_task
  • 几个Service

    • RepositoryService --> 操作部署表、流程定义表、二进制表等静态资源信息表
    • RuntimeService --> 操作流程实例表、任务表等动态信息表
    • TaskService --> 操作任务表
    • HistoryService --> 操作历史表
    • IdentityService --> 操作用户表、组表、关系表

示例代码如下:

package com.itheima.activiti;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.ZipInputStream;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.apache.commons.io.FileUtils;
import org.junit.Test;

public class ActivitiAPITest {

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    /**
     * 部署 流程定义 
     *      方式一:加载单个的流程定义文件 方式二:加载zip文件
     * @throws FileNotFoundException 
     */

    @Test
    public void test1() throws FileNotFoundException {
        DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();

        // 方式一:加载单个的流程定义文件
        // deploymentBuilder.addClasspathResource("qingjialiucheng.bpmn");
        // deploymentBuilder.addClasspathResource("qingjialiucheng.png");
        // Deployment deployment = deploymentBuilder.deploy();

        // 方式二:加载zip文件
        // 从根路径下读一个文件,并返回该文件对应的输入流,使用类加载器对象获取classpath路径下的文件
        // ZipInputStream zipInputStream = new ZipInputStream(this.getClass().getClassLoader().getResourceAsStream("process.zip"));
        // 直接new一个文件输入流,文件位置灵活
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(new File("e:\\process.zip"))); // 将.png文件和.bpmn文件压缩到process.zip中
        deploymentBuilder.addZipInputStream(zipInputStream);
        deploymentBuilder.deploy();
    }

    /**
     * 删除 流程定义
     */

    @Test
    public void test2() {
        String deploymentId = "501"// 部署id
        boolean cascade = true// 级联删除(cascade级联),很少使用
        processEngine.getRepositoryService().deleteDeployment(deploymentId, cascade);
    }

    /**
     * 查询 流程定义
            processEngine.getRepositoryService().createDeploymentQuery().list();
            processEngine.getRepositoryService().createProcessDefinitionQuery().list();
            processEngine.getRuntimeService().createProcessInstanceQuery().list();
            processEngine.getTaskService().createTaskQuery().list();
            processEngine.getIdentityService().createUserQuery().list();
            processEngine.getHistoryService().createHistoricActivityInstanceQuery().list();
     */

    @Test
    public void test3() {
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        query.processDefinitionKey("qingjialiucheng"); // 根据key过滤,在流程定义表中,key是流程的标识,key相同,表示同一流程
        query.orderByProcessDefinitionVersion().asc();
        List<ProcessDefinition> list = query.listPage(010);
        for (ProcessDefinition processDefinition : list) {
            System.out.println(processDefinition.getId());
        }
    }

    /**
     * 方法一:查询 部署对应的流程定义文件的名称和输入流,该方式需要部署的id和文件名称,不够直接
     * 案例中两种方式都会用到
     *      我们知道,一旦我们将流程部署完毕,我们就跟项目中的process目录没有关系了,我们在浏览器上看到的png图片文件来自于二进制表,图片已经被我们存成二进制数据了
     * @throws FileNotFoundException 
     */

    @Test
    public void test4() throws Exception {
        String deploymentId = "501"// 部署id
        List<String> names = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId);
        for (String name : names) {
            // 获取文件输入流
            InputStream in = processEngine.getRepositoryService().getResourceAsStream(deploymentId, name);

            // 方式一:原始方法
            // 将文件输入流读取出来,然后通过文件输出流写入到磁盘上
            /*
            OutputStream out = new FileOutputStream(new File("e:\\" + name));
            byte[] b = new byte[1024];
            int len = 0;
            while ((len = in.read(b)) != -1) {
                out.write(b, 0, len);
            }
            out.close();
            in.close();
            */


            // 方式二:通过工具类
            FileUtils.copyInputStreamToFile(in, new File("e:\\" + name));
            in.close();
        }
    }

    /**
     * 
     * 方法二:查询 部署对应的流程定义文件的输入流,该方式只需要流程定义id即可,够直接,但注意,该方法只能返回图片的输入流,xml的输入流返回不了
     * 案例中两种方式都会用到
     * @throws FileNotFoundException 
     */

    @Test
    public void test5() throws Exception {
        String processDefinitionId = "qingjialiucheng:1:504"// 流程定义id
        InputStream pngStream = processEngine.getRepositoryService().getProcessDiagram(processDefinitionId); 
        FileUtils.copyInputStreamToFile(pngStream, new File("e:\\abc.png"));
    }

    /**
     * 根据流程定义启动流程实例
     *      方式一:根据流程定义id启动流程实例
     *      方式二:根据流程定义key启动流程实例(建议方式) --> 因为该方式可以根据当前最新版本的流程定义启动流程实例(即最新version值)
     */

    @Test
    public void test6() throws Exception {
        // 方式一:根据流程定义id启动流程实例
        // String processDefinitionId = "qingjialiucheng:1:504"; // 流程定义id
        // ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceById(processDefinitionId);
        // System.out.println(processInstance.getId() + " " + processInstance.getProcessDefinitionId());

        // 方式二:根据流程定义key启动流程实例
        String processDefinitionKey = "qingjialiucheng";
        ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey);
        System.out.println(processInstance.getId() + " " + processInstance.getProcessDefinitionId());
    }

    /**
     * 查询 流程实例
     */

    @Test
    public void test7() throws Exception {
        List<ProcessInstance> list = processEngine.getRuntimeService().createProcessInstanceQuery().list();
        for (ProcessInstance processInstance : list) {
            System.out.println(processInstance.getId());
        }
    }

    /**
     * 删除 流程实例
     */

    @Test
    public void test8() throws Exception {
        String processInstanceId = "701";
        String deleteReason = "女票过来安慰我了,突然又不想请假了";
        processEngine.getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason);
    }

    /**
     * 查询 个人任务
     */

    @Test
    public void test9() throws Exception {
        TaskQuery query = processEngine.getTaskService().createTaskQuery();
        query.taskAssignee("张三"); // 任务处理人
        query.orderByTaskCreateTime().desc(); // 根据任务创建时间排序,降序
        List<Task> list = query.list();
        for (Task task : list) {
            System.out.println(task.getId());
        }
    }

    /**
     * 办理 个人任务
     */

    @Test
    public void test10() throws Exception{
        String taskId = "1004"// 任务id
        processEngine.getTaskService().complete(taskId);
    }
}
10-15 21:27