Activiti工作流(一):OA 上的那些请假流程如何快速实现呢?

大家好,我是 杰哥

在公司中,每个人都需要经常创建或者审批一些流程,比如说转正申请请假流程出差申请等等

那么,你有没有想过,要是你,你会如何实现这些流程的控制逻辑呢?

比如说请假申请,首先需要提前定义好一个流程模板吧,包括当请假天数大于 3 天的话,需要公司级的领导审批等这些规则

然后当某个员工发起请假申请时,根据这个员工所在的部门以及请假天数等信息,便可以确定其具体的流程信息,包括每个节点的审批人

在任何一个审批节点,都需要看到这个流程的历史审批信息以及将来要走的流程吧,所以还需要将完成的和将要完成的任务保存下来

你应该很容易想到,我们可以通过表的字段状态来进行流程的这些逻辑控制

但是呢,简单的流程的话,还可以实现,流程一旦复杂一点,不仅需要实现的代码会增加,而且需要操作的表的数量也特别多,逻辑只会更复杂

那么,有没有现成的工作流框架,可以直接拿来使用,轻松实现这些逻辑,实现流程的自动化控制呢?

答案是肯定的,今天带领大家认识的就是一个专门用来管理工作流的框架:Activiti

一 理论篇

1 实现逻辑

Activiti 就是使用 BPMN 2.0 进行流程建模、流程执行管理,那么,BPMN 又是什么呢?

BPMN(Business Process Model AndNotation)- 业务流程模型和符号 是由 BPMI(BusinessProcess Management Initiative)开发的一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程

定义好的流程模板,最终会被存储至数据库表中,而它的实现逻辑其实就是封装了诸多流程控制的表的操作,提供给开发人员一些快捷操作的 API ,来进行快捷的工作流管理

2 实现步骤

使用 Activiti 这种工作流框架大致都分为以下几个步骤:

  • 流程定义

  • 部署流程定义

  • 启动流程实例

  • 查询当前用户的待办任务

  • 完成任务

说明:

流程定义就相当于是一个个的流程模板,比如预先定义好的请假流程、出差流程模板,而流程实例则是由各个员工发起的实实在在的流程,流程实例一旦创建,那么这个流程需要经历哪些审批环节,每个环节由哪个(或者哪些)人负责审批,便完全确定下来了

流程定义与流程实例的关系,你也可以理解为类与对象实例的关系。一个是静态的,一个则是真正被拿来使用的,相对动态

3 Service

Activiti 提供了几个 Service 类,用来管理工作流,常用的有以下四项:

  • 1)RepositoryService:提供流程定义和部署等功能。比如说,实现流程的的部署、删除,暂停和激活以及流程的查询等功能

  • 2)RuntimeService:提供了处理流程实例不同步骤的结构和行为。包括启动流程实例、暂停和激活流程实例等功能

  • 3)TaskService:提供有关任务相关功能的服务。包括任务的查询、删除以及完成等功能

  • 4)HistoryService:提供 Activiti 引擎收集的历史记录信息服务。主要用于历史信息的查询功能

    还有以下两项:

  • **1)**ManagementService:job 任务查询和数据库操作

  • **2)**DynamicBpmnService:无需重新部署就能修改流程定义内容

4 认识表结构

图片

二 实战篇

以下进入实战环节,分别从生成 25 张表运行第一个 Activiti 流程实例,快速认识这个工作流框架的简单易用性

这两个例子,采用 Activiti7 版本,需要预先引入如下依赖:

<dependencies>

    <dependency>
         <groupId>org.activiti</groupId>
         <artifactId>activiti-engine</artifactId>
         <version>7.1.0.M6</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring</artifactId>
        <version>7.1.0.M6</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-model</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-converter</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-json-converter</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-layout</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <dependency>
        <groupId>org.activiti.cloud</groupId>
        <artifactId>activiti-cloud-services-api</artifactId>
        <version>7.0.0.Beta1</version>
    </dependency>
    <!-- mysql 连接-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>

    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>

    <!--lombok 工具 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.17.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

然后来看看如何生成 25 张表

(一) 生成 25 张表

1 代码编写

1) activiti.cfg.xml 文件配置

我们在 resources 目录下,创建一个文件 activiti.cfg.xml ,主要用来配置数据库的信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">

    <!-- 配置数据库 dataSouce 信息-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/activiti"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        
        <property name="maxActive" value="3"/>
        <property name="maxIdle" value="1"/>
    </bean>


    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <property name="dataSource" ref="dataSource"/>
        <!-- 配置模式 true : 自动创建和更新表 -->
        <property name="databaseSchemaUpdate" value="true" />
    </bean>

</beans>

Activiti7 支持 h2、mssql、mysql 、db2、oracle hsql以及 postgres7 种数据库,我们在这里使用 mysql 数据库

分别配置 mysql 数据库的 url数据库驱动以及用户名密码等信息,然后通过定义类型为 StandaloneProcessEngineConfiguration 的对象 processEngineConfiguration,并指定其配置模式为databaseSchemaUpdate,表示每次启动项目,均会检查数据库中的表,如果不存在,则创建,否则检查更新

2)代码编写

通过  ProcessEngines.getDefaultProcessEngine() 方法,实现 25 张表的自动创建

@Slf4j
public class TestCreat {
    /**
     * 数据库表结构的创建
     */
    @Test
    public void testCreateDbTable(){
        /**
         * 创建 processEngine 时,会自动加载 /resources 目录下的 activiti.cfg.xml 文件
         * 进行 mysql 数据库中表的创建
         */
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        log.info(processEngine.getName());
    }

在创建 processEngine 时,Activiti 会自动加载 /resources 目录下的 activiti.cfg.xml 文件进行 mysql 数据库中表的创建

感兴趣的话,可以一路点下去,看看这个方法的源码。

一直到 ProcessEngines 的 init() 方法(当系统判断,当前还未在数据库中进行过表的创建动作时),会发现其加载的就是 resource 目录下的 activiti.cfg.xml 文件

图片

当然要是 Spring 项目,则加载的是 activiti-context.xml 文件。Spring 与 Activiti 的集成,本篇暂且不看,留在下一篇去讨论

2 运行

启动运行,发现控制台中日志打印如下

图片

通过执行 org/activiti/db/create/activiti.mysql.create.engine.sql 这个 sql 文件,完成了 Activiti 所需要的 25 张表的创建动作

检查数据库,你会发现,数据库中便生成了 25 张表

图片

(二) 第一个 activiti 流程实例

Activiti 在 eclipseIDEA 中均有流程定义的插件,在 IDEA 中需要安装的插件如下

图片

插件安装完毕之后,重启 IDEA ,然后在 resources 目录下创建一个文件夹 bpmn(专门用于存储 Activiti 的流程文件)

1 流程定义

1)流程文件创建

如下图所示,创建我们的第一个 bpmn 文件图片

右键,New -> New Activiti 6.x BPMN 2.0 file->填写文件名称,创建好之后发现,其实该文件就是一个 xml 文件

右键该文件,点击:View BPMN(Activiti) Diagram,便可以进行流程的设计了

图片

2)流程文件设计

在空白处,右键点击

图片

可以看到有分别有流程启动事件活动架构网关边界事件紧急捕获紧急抛出事件以及结束事件等,点击对应的右三角,便可以创建对应的事件或活动

比如

图片

图片

通过拖动各个事件右上角的箭头,即可画一条连线。这里我们创建了一个比较简单的请假流程

图片

需要注意,点击空白处,可以更新流程定义的名称与 id,这里,我们的流程定义 idleaveApplication流程定义名称请假流程定义

然后,分别指定两个活动的名称与负责人。一个流程模板每个工作节点的负责人当然不可能是固定的,所以,在这里我们采用 Activiti 所支持的 UEL 表达式,将负责人定义为动态参数:assignee0assignee1

- 创建申请

图片

- 审批申请

图片

好了,这样我们就定义好了一个简单的流程,接下来,进行流程的部署操作,也就是说需要把我们所创建的流程定义文件,真正与数据库的表关联起来

2 流程部署

/**
 * 部署流程 --RepositoryService
 */
@Test
public void testDeploy(){

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = processEngine.getRepositoryService();

    Deployment deploy = repositoryService.createDeployment()
            .name("请假申请流程定义")
            .addClasspathResource("bpmn/leaveApplication.bpmn20.xml")
            .deploy();
    log.info("流程部署id:{}",deploy.getId());
    log.info("流程部署名称:{}",deploy.getName());
}

因为需要对流程本身进行部署操作,而流程本身属于静态资源,需要进行存储的,所以采用 RepositoryService 这个服务的 API  进行部署操作

运行该测试方法,发现控制台对以下三张表分别进行了插入记录操作

图片

act_re_procdef(流程定义数据表)
act_re_deployment(部署信息表)
act_ge_bytearray(二进制数据表,存储流程定义时的资源文件信息,如这里的 bpmn/leaveApplication.bpmn20.xml 文件)

也就是说我们将这个流程定义文件中的信息分别存入了数据库中关于流程定义的表中

3 启动流程实例

/**
 * 启动流程 --  RuntimeService
 *
 */
@Test
public void testStartProcess(){

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    Map<String,Object> map = new HashMap<>();
    map.put("assignee0","张三");
    map.put("assignee1","王经理");
    ProcessInstance instance = runtimeService.startProcessInstanceByKey(key,map);

    System.out.println("流程定义id:"+instance.getProcessDefinitionId());
    System.out.println("流程实例 id:"+instance.getId());
    System.out.println("当前活动的id:"+instance.getActivityId());

}

启动流程时,分别指定各个任务的负责人,上述代码表示该请假流程是由张三发起的,审批人是王经理

由于启动一个流程,就表示是流程实例被运行起来了,那么,就使用 RuntimeService 的 startProcessInstanceByKey() 方法进行,其中参数 key 表示指定所启动的流程定义,而 map 则是为其启动之后的流程实例赋值

启动一个流程实例,我们的预期就是完成 Start event 事件,流程进入到张三的“创建申请”这个步骤

那么就会执行完 Start event 这个任务节点,并将相应的信息存入历史信息表中,然后将下一步要执行的“创建申请”这个任务节点的信息,存入运行时信息表中。具体是怎样的呢?

我们运行该启动方法,观察控制台,发现所操作的表如下:

图片

1) 历史表
  
ACT_HI_VARINST    历史变量表
ACT_HI_TASKINST   历史任务实例表
ACT_HI_PROCINST   历史流程实例表
ACT_HI_ACTINST    历史节点表
ACT_HI_IDENTITYLINK 历史人员参与表
  
2) 运行时表
  
ACT_RU_EXECUTION   运行时流程执行实例
ACT_RU_TASK        运行时任务节点表
ACT_RU_IDENTITYLINK  运行时人员参与表
ACT_RU_VARIABLE      运行时变量表

通过查看这些表的变化,结果是符合上述预期的。比如查看一下 ACT_RU_TASK 表,会发现任务节点信息已被存入表中,表示当前将要进行的任务就是创建申请

4 查询代表任务

/**
 * 查询个人待执行的任务 -- TaskService
 */
@Test
public void testFindPersonalTaskList(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();

    List<Task> taskList = taskService.createTaskQuery()
            .processDefinitionKey(key)
            .taskAssignee("张三")
            .list();
    for (Task task: taskList) {
        System.out.println("流程实例id:"+task.getProcessInstanceId());
        System.out.println("任务id:"+task.getId());
        System.out.println("任务负责人:"+task.getAssignee());
        System.out.println("任务名称:"+task.getName());

    }
}

通过 TaskService 进行查询,分别通过请假流程的 key用户名称,查询该用户名下的所有任务

5 完成任务

@Test
    public void completeProcess(){
        
       String assignee = "张三";

        /**
         * 完成任务
         */

        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        Task task = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assignee)
                .singleResult();
        
        taskService.complete(task.getId());

    }

首先查询到所需要完成的任务,然后通过 TaskService 的 complete() 方法进行任务的完成动作(当然实际业务中,需要先进行任务的具体操作,比如添加自己的审批意见等之后才会点击任务的完成,进入下一步骤)

完成任务时,通过查看日志,发现其与启动任务的任务节点所操作的表类似,就是将当前节点所完成的任务转存到历史信息表中,再将下一步需要完成的任务存入运行时信息表中

这里,我们完成创建申请的任务之后,表里面当前需要执行的任务,变成了“审批申请”

图片

说明任务已经成功得到了执行 接下来,采用同样的方法,完成审批申请的任务,我们可以猜想一下,表中的数据又会得到怎样的变化

/**
     * 完成任务 -- TaskService
     */
    @Test
    public void completeTask(){
//        String assignee = "张三";
        String assignee = "王经理";
        /**
         * 完成任务
         */

        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        Task task = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assignee)
                .singleResult();

        taskService.complete(task.getId());


    }

是的,依旧是一样的操作,将当前节点所完成的任务转存到历史信息表中,只是这里已经是最后一步任务了,所以执行完成之后,也会直接执行 End event

所以,运行完这个步骤之后,我们会发现 act_ru_task 表变成了空的,而 act_hi_taskinst 表中可以看到历史所有执行过的任务实例,包括启动事件、创建申请、审批申请以及结束事件

图片

总结

好了,到这里,我们就轻松完成了一个请假流程的流程的定义流程定义的部署流程实例的启动任务完成的所有操作

会发现,其实 Activiti 的逻辑其实就是通过控制表来进行具体的任务状态操作的。每一步的操作都需要根据一定的逻辑关系,更新数张表来进行控制的

而这些操作,都由 Activiti 框架帮助我们封装好了,提供给我们的只是一个个服务的 API ,从而大大简化了工作流的操作,所以还不快用起来?

文章演示代码地址:

github.com/helemile/Sp…

嗯,就这样。每天学习一点,时间会见证你的强大~

欢迎大家关注我们的公众号,一起持续性学习吧~

往期精彩回顾

总结复盘

架构设计读书笔记与感悟总结

带领新人团队的沉淀总结

复盘篇:问题解决经验总结复盘

网络篇

网络篇(四):《图解 TCP/IP》读书笔记

网络篇(一):《趣谈网络协议》读书笔记(一)

事务篇章

事务篇(四):Spring事务并发问题解决

事务篇(三):分享一个隐性事务失效场景

事务篇(一):毕业三年,你真的学会事务了吗?

Docker篇章

Docker篇(六):Docker Compose如何管理多个容器?

Docker篇(二):Docker实战,命令解析

Docker篇(一):为什么要用Docker?

..........

SpringCloud篇章

Spring Cloud(十三):Feign居然这么强大?

Spring Cloud(十):消息中心篇-Kafka经典面试题,你都会吗?

Spring Cloud(九):注册中心选型篇-四种注册中心特点超全总结

Spring Cloud(四):公司内部,关于Eureka和zookeeper的一场辩论赛

..........

Spring Boot篇章

Spring Boot(七):你不能不知道的Mybatis缓存机制!

Spring Boot(六):那些好用的数据库连接池们

Spring Boot(四):让人又爱又恨的JPA

SpringBoot(一):特性概览

..........

翻译

[译]用 Mint 这门强大的语言来创建一个 Web 应用

【译】基于 50 万个浏览器指纹的新发现

使用 CSS 提升页面渲染速度

WebTransport 会在不久的将来取代 WebRTC 吗?

.........

职业、生活感悟

你有没有想过,旅行的意义是什么?

程序员的职业规划

灵魂拷问:人生最重要的是什么?

如何高效学习一个新技术?

如何让自己更坦然地度过一天?

..........

猜你喜欢

转载自juejin.im/post/7120239200293077000