工作流引擎之activiti入门

1.什么是Activiti

在解释activiti之前我们看一下什么是工作流。 工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。 我的理解是,工作流将一套大的业务逻辑分解成业务逻辑段, 并统一控制这些业务逻辑段的执行条件,执行顺序以及相互通信。 实现业务逻辑的分解和解耦。 Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。 BPMN即业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)。

BPMN的流程图长这样子

activiti5.13使用了23张表支持整个工作流框架,底层使用mybatis操作数据库。这些数据库表为

1)ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义相关的静态资源(图片,规则等)。 2)ACT_RU_*: 'RU'表示runtime。 运行时表,包含流程实例,任务,变量,异步任务等运行中的数据。流程结束时这些记录会被删除。 3)ACT_ID_*: 'ID'表示identity。 这些表包含用户和组的信息。 4)ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等。 5)ACT_GE_*: 通用数据,bytearray表保存文件等字节流对象。

工作流进行的基本过程如下: 定义流程(框架外) -> 部署流程定义 -> 启动流程实例, 框架移动到任务1 -> 拾取组任务 -> 办理个人任务, 框架移动到任务2 -> 拾取组任务 -> 办理个人任务...

组任务是多个用户都可以完成的任务。没有组任务直接办理个人任务; 有组任务需先通过拾取将组任务变成个人任务, 然后再办理。

个人任务/组任务在表中的区别

个人任务: 表act_ru_task的ASSIGNEE段即指定的办理人

组任务: 表act_ru_task的ASSIGNEE段为null, 相关信息在表act_ru_identitylink中, 组任务1见userid段; 组任务2见groupid段, 当然还需查询act_id_xxx表才能精确到人.

2.Activiti的使用

2.1 创建processEngine

processEngine控制着工作流整个流程

public class processEngine {
    @Test
    public void createProcessEngine1() {
        String resource = "activiti-context.xml";    // 配置文件
        String beanName = "processEngineConfiguration";  // 配置文件中bean name
        // 从配置文件创建配置对象
        ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(resource, beanName);
        // 根据配置创建引擎对象
        ProcessEngine processEngine = config.buildProcessEngine();
    }

    /**
     *  一条语句创建processEngine, 要求:
     * 1、配置文件必须在classpath根目录下
     * 2、配置文件名必须为activiti-context.xml或activiti.cfg.xml
     * 3、工厂对象的id必须为processEngine
     */
    @Test
    public void createProcessEngine2() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    }
}
<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:///test_activiti"/>
        <property name="jdbcUsername"  value="root"/>
        <property name="jdbcPassword"  value="root"/>
        <!-- 创建processEngine时, activiti自动创建23张表 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
    <!-- 使用配置创建引擎对象 -->
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
        <property name="processEngineConfiguration" ref="processEngineConfiguration"/>
    </bean>
</beans>

当然, 可以与spring进一步整合, 使用spring方式获取processEngine. applicationContext.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="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql:///activiti_day2" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 流程引擎配置对象 -->
    <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 注入事务管理器对象 -->
        <property name="transactionManager" ref="transactionManager"/>
        <property name="databaseSchemaUpdate" value="true" />
    </bean>
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
        <property name="processEngineConfiguration" ref="processEngineConfiguration" />
    </bean>
</beans>

2.2 部署流程定义

流程是由用户通过bpmn等文件(底层xml)定义的, 即上面列举的的bpmn流程图

定义好的流程需要部署给activiti才能被其使用

/**
     * 部署流程定义 
     * 一套定义文件只有一个流程定义Key, 但可以被部署多次形成多个版本(部署表里多个id和流程定义表里多个id)
     * 涉及的表:act_re_deployment(部署表)、act_re_procdef(流程定义表)、act_ge_bytearray(二进制表)
     */
    @Test
    public void test() throws FileNotFoundException {
        DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
        // 逐个文件部署
        // deploymentBuilder.addClasspathResource("qjlc.bpmn");
        // deploymentBuilder.addClasspathResource("qjlc.png");
        // 压缩文件打包部署, 推荐
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(new File("d:\\processDef.zip")));
        deploymentBuilder.addZipInputStream(zipInputStream );

        Deployment deployment = deploymentBuilder.deploy();
    }

2.3 启动流程实例

/**
     * 启动一个流程实例
     * 涉及的表:
     * act_ru_execution(流程实例表), 管理流程进度
     * act_ru_task(任务表), 进行到哪一个流程的哪一个任务, 该由谁完成
     */
    @Test
    public void test() throws Exception{
        String processDefinitionKey = "qjlc";
        //方式一:根据流程定义id启动流程实例
        //String processDefinitionId = "qjlc:6:904";
        //ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceById(processDefinitionId);

        //方式二:根据流程定义Key启动流程实例   推荐!流程定义有多个版本时会选择最新版本
        ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey);
    }

2.4 办理任务

/**
    * 办理任务, 办理后框架自动移动到下一任务
    * 涉及的表: act_ru_execution(流程实例表)、act_ru_task(任务表)
    */
    @Test
    public void test() throws Exception{
        String taskId = "1304";
        processEngine.getTaskService().complete(taskId);
    }

2.5 其他操作

/**
    * 查询流程定义
    * 涉及的表:act_re_procdef
    */
    @Test
    public void test(){
        ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
        // 查询条件过滤
        query.processDefinitionKey("qjlc");
        query.orderByProcessDefinitionVersion().asc();
        List<ProcessDefinition> list = query.listPage(0, 10);
        for (ProcessDefinition processDefinition : list) {
            System.out.println(processDefinition.getId());
        }
    }

activiti中查询的套路: processEngine.getXXXService().createXXXQuery().list()/singleResult() processEngine.getRepositoryService().createDeploymentQuery().list(); // 查询部署 processEngine.getRuntimeService().createProcessInstanceQuery().list(); // 查询流程实例 processEngine.getTaskService().createTaskQuery().list(); // 查询个人任务 processEngine.getIdentityService().createUserQuery().list(); // 查询用户 processEngine.getHistoryService().createHistoricActivityInstanceQuery().list(); //查询历史 过滤条件 查询个人任务 query.taskAssignee() 查询组任务 query.taskCandidate()

几个javabean(和表对应): 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----操作用户表、组表、关系表

// 删除流程定义
    @Test
    public void test1(){
        String deploymentId = "101";  //部署id
        boolean cascade = false;  // 级联删除, 设置为true的话, 有正在跑的流程实例及任务也会被删除
        processEngine.getRepositoryService().deleteDeployment(deploymentId, cascade);
    }
    // 删除流程实例
    @Test
    public void test2() throws Exception{
        String processInstanceId = "1201";
        String deleteReason = "不请假了";  // 可以添加删除原因
        processEngine.getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason);
    }
  // 根据部署id, 获取定义文件
    @Test
    public void test3() throws Exception{
        String deploymentId = "201"; //部署id
        // 先获得定义文件的名字
        List<String> names = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId);
        for (String name : names) {
            InputStream in = processEngine.getRepositoryService().getResourceAsStream(deploymentId, name);
            FileUtils.copyInputStreamToFile(in, new File("d:\\"+name));
            in.close();
        }
    }
    // 根据流程定义id, 获取定义文件
    @Test
    public void test4() throws Exception{
        String processDefinitionId = "qjlc:6:904"; //流程定义id
        InputStream pngStream = processEngine.getRepositoryService().getProcessDiagram(processDefinitionId);
        FileUtils.copyInputStreamToFile(pngStream, new File("d:\\abc.png"));
    }

通过javabean能访问到某些需要的字段, 例如

processInstance.getActivityId() -> 当前执行的任务名

processDefinition.getDiagramResourceName() -> 定义文件中图片的名字

2.6 流程变量

多个任务间可以通过流程变量通信.

流程变量以key-value形式存放, 存于表 act_ru_variable. 在同一流程实例里, 不同方式设置变量, key相同时会覆盖

// 启动流程实例时 设置流程变量
    @Test
    public void test1() {
        String processDefinitionKey = "bxlc";
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("key", "value");
        ProcessInstance pi = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, variables);
    }
    // 办理任务时 设置流程变量, 更实用!
    @Test
    public void test2() {
        String taskId = "206";
        Map<String, Object> variables = new HashMap<>();
        variables.put("key", "value");
        processEngine.getTaskService().complete(taskId, variables);
    }
    // 通过RuntimeService 设置流程变量
    @Test
    public void test3() {
        String executionId = "201"; // 流程实例id
        Map<String, Object> variables = new HashMap<>();
        variables.put("key", "value");
        //processEngine.getRuntimeService().setVariable(executionId, variableName, value);
        processEngine.getRuntimeService().setVariables(executionId, variables);
    }
    // 通过TaskService 设置流程变量
    @Test
    public void test4() {
        String taskId = "304";
        String key = "key";
        Object value = "value";
        processEngine.getTaskService().setVariable(taskId , key, value);
    }
// 通过RuntimeService 获取流程变量
    @Test
    public void test5() {
        String executionId = "201";
        Object value = processEngine.getTaskService().getVariable(executionId, "user");
        System.out.println(value);
    }
     // 通过TaskService 获取流程变量
    @Test
    public void test6() {
        String taskId = "304";
        Object value = processEngine.getTaskService().getVariable(taskId, "user");
        System.out.println(value);
    }

流程变量还可以通过在定义流程用表达式${}. 框架在该段任务执行前从act_ru_variable表里动态获取

另外, 启动流程实例还有一个重载函数, 除了流程变量variables还能指定业务主键businessKey

processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, businessKey, variables);

businessKey一般设置为业务表的主键值, 在使用activiti的时候, 通过查询业务表主键, 能方便地查询出业务的最新状态

2.7 组任务

组任务1

// 查询组任务
    @Test
    public void test1() {
        TaskQuery query = processEngine.getTaskService().createTaskQuery();
        // 使用候选人查询组任务
        String candidateUser = "财务二";
        query.taskCandidateUser(candidateUser);
        List<Task> list = query.list();
        for (Task task : list) {
            System.out.println(task.getId());
        }
    }
    // 拾取组任务
    @Test
    public void test2() {
        String taskId = "1102";
        processEngine.getTaskService().claim(taskId , "财务二");
    }
    // 办理组任务, 无需指定办理人
    @Test
    public void test3() throws Exception{
        String taskId = "1102";
        processEngine.getTaskService().complete(taskId);
    }

组任务2

// activiti使用自己的用户与组的权限表, 因此需要设置. 但需注意要与框架外用户/组同步设置
    @Test
    public void test2() {
        // 创建组
        Group group = new GroupEntity();
        group.setId("财务组");
        processEngine.getIdentityService().saveGroup(group);
        // 创建用户
        User user = new UserEntity();
        user.setId("2");
        processEngine.getIdentityService().saveUser(user);
        // 维护用户与组的关系
        processEngine.getIdentityService().createMembership("2", "财务组");
    }
    // 查询组任务
    @Test
    public void test2() {
        TaskQuery query = processEngine.getTaskService().createTaskQuery();
        String candidateUser = "2";
        // 使用候选人过滤
        query.taskCandidateUser(candidateUser);
        // 使用组过滤
        //query.taskCandidateGroup("财务组");
        List<Task> list = query.list();
        for (Task task : list) {
            System.out.println(task.getId());
        }
    }
    // 拾取组任务
    @Test
    public void test3() {
        String taskId = "1902";
        processEngine.getTaskService().claim(taskId , "2");
    }
    // 办理组任务略

2.8 排他网关

设置分支条件

3. 一些使用经验

1)

考虑到工作流中的一个任务, 对应一个业务段, 可以将taskDefinitionKey设置成strus action类的method, 使之具有一定的通用性

2)

两种对流程定义的查询, 后者能获得更多定义的细节信息 processDefinitionEntity.findActivity(taskId) 工作流中某任务的信息

repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult()

(ProcessDefinitionEntity) repositoryService.getProcessDefinition(processDefinitionId)

本文分享自微信公众号 - Linyb极客之路(gh_c420b2cf6b47)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-04-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端大白专栏

react dva如何获取被form包裹的子组件函数

42990
来自专栏Java帮帮-微信公众号-技术文章全总结

回顾Java 8 9 10的新特性,展望即将来临的11和明年的12【大牛经验】

1997年4月2日,JavaOne会议召开,参与者逾一万人,创当时全球同类会议纪录;

1.8K30
来自专栏C/C++基础

腾讯2016春季校园实习招聘技术岗初试(一面)问题汇总(CC++后台)

2016.4.11日广州参加了腾讯的CC++后台技术一面,安全技术类的面试。面试官人很温和,经历了大概70分钟的问答,特将遇到的面试问题汇总如下,自己总结学习,...

10110
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第2章 追溯Drools5的使用

2.1 Drools5简述 上面已经提到Drools是通过规则编译、规则收集和规则的执行来实现具体功能的。Drools5提供了以下主要实现API: Knowl...

45780
来自专栏Leetcode名企之路

【redis】Redis有哪些数据结构

String 这应该是应用最广泛的了,简单的 key-value 类型。value 不仅可以是 String,也可以是数字。还可以享受 Redis 的定时持...

83420
来自专栏Java3y

Activiti就是这么简单

Activiti介绍 什么是Activiti? Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务...

88880
来自专栏向治洪

Mpg123源代码详解

Mpg123与libmad一样,支持mpeg1,2,2.5音频解码。目前来看mpg123比libmad支持了网络播放功能。而且libmad基本上开源社区在200...

25070
来自专栏AI派

真是绝了!史上最详细的Jupyter Notebook入门教程

Jupyter Notebook 是一个在浏览器中使用的交互式的笔记本,可以实现将代码、文字完美结合起来,它的受众群体大多数是一些从事数据科学领域相关(机器学习...

1.8K80
来自专栏CSDN技术头条

QtQuick 系列教程之 QML 与 C++ 交互

QML 作为一种灵活高效的界面开发语言已经越来越得到业界的认可。QML 负责界面,C++ 负责逻辑,这也是 Qt 官方推荐的开发方式。那么 QML 与 C++ ...

32330
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误、不要在不恰当的场合下引发异常、重新引发异常时使用inner Exception]

  自从.NET出现后,关于CLR异常机制的讨论就几乎从未停止过。迄今为止,CLR异常机制让人关注最多的一点就是“效率”问题。其实,这里存在认识上的误区,因为正...

14620

扫码关注云+社区

领取腾讯云代金券