系列博客介绍
在常用的ERP系统、OA系统的开发中,工作流引擎是一个必不可少的工具。现在很多公司、企业办公都使用大公司的产品比如阿里巴巴的钉钉、腾讯的企业微信、头条的飞书等。但是对于我们程序员来说,掌握好 Activiti 这门技术,是有必要的。
标准工作流的图:
为啥要用工作流?
在没有专门的工作流引擎之前,我们之前为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。这样不用角色的用户,通过状态字段的取值来决定记录是否显示。
针对有权限可以查看的记录,当前用户根据自己的角色来决定审批是否合格的操作。如果合格将状态字段设置一个值,来代表合格;当然如果不合格也需要设置一个值来代表不合格的情况。
这是一种最为原始的方式。通过状态字段虽然做到了流程控制,但是当我们的流程发生变更的时候,这种方式所编写的代码也要进行调整。
Activiti是一个工作流引擎, activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
OK,少说理论,直接进入主题。这篇博客是基于一定的 Java 基础上的,只说重点,细节需要自己去掌握。
Activiti 使用步骤
1、部署activiti
Activiti是一个工作流引擎(其实就是一堆jar包API),业务系统访问(操作)activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起。
2、流程定义
使用activiti流程建模工具(activity-designer)定义业务流程(.bpmn文件) 。.bpmn文件就是业务流程定义文件,通过xml定义业务流程。
3、流程定义部署
activiti部署业务流程定义(.bpmn文件)。使用activiti提供的api把流程定义内容存储起来,在Activiti执行过程中可以查询定义的内容。
Activiti执行把流程定义内容存储在数据库中。
4、启动一个流程实例
流程实例也叫:ProcessInstance
启动一个流程实例表示开始一次业务流程的运行。
在员工请假流程定义部署完成后,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响。
5、用户查询待办任务(Task)
因为现在系统的业务流程已经交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不需要开发人员自己编写在sql语句查询。
6、用户办理任务
用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如采购单创建后由部门经理审核,这个过程也是由activiti帮我们完成了。
7、流程结束
当任务办理完成没有下一个任务结点了,这个流程实例就完成了。
Activiti 环境入门
JDK1.8,IDEA,MySQL5.7 数据库。
1、IDEA 下载 actiBPM 插件。
先下载 actiBPM 插件,重启 IDEA。
为了防止后面使用时 bpmn 中文乱码,请先配置以下几点:
1、修改 IDEA 默认编码 UTF-8
2、进入到 IDEA 安装目录,进入到 bin 目录下,找到这2个 .vmoptions 的文件,用记事本打开。
修改这两个文件,在其尾部增加一句话:-Dfile.encoding=UTF-8
一定注意,不要有空格,否则重启IDEA时会打不开,然后 重启IDEA。
3、如果无效,那么则找到你的 idea 配置文件,如果你默认安装,有可能在 C 盘下,比如:C:\Users\XXX\.IntelliJIdea2018.2\config,找到后进入config目录,然后在这个目录下的idea64.exe.vmoptions文件尾部增加 -Dfile.encoding=UTF-8。
最后,重启 IDEA 。
2、创建 Maven 项目,生成 Activiti 的 25 张表
创建 maven 项目,pom.xml 如下:
<?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.study</groupId>
<artifactId>study-activiti</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
</parent>
<dependencies>
<!--Spring boot 集成包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 阿里巴巴的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<!-- 该 starter 会扫描配置文件中的 DataSource,然后自动创建使用该 DataSource 的 SqlSessionFactoryBean,并注册到 Spring 上下文中 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- 数据库MySQL 依赖包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- mybatis 依赖包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--添加 Activiti工作流的支持-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
使用 activiti 提供的默认方式来创建mysql的表。
默认方式的要求是在 resources 下创建 activiti.cfg.xml 文件,注意:默认方式目录和文件名不能修改,因为activiti的源码中已经设置,到固定的目录读取固定文件名的文件。
默认方式要在在 activiti.cfg.xml 中bean的名字叫 processEngineConfiguration,名字不可修改
activiti.cfg.xml 完整代码如下:(需要创建 study_activiti 数据库,根据实际数据库账号密码配置)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 默认id对应的值 为processEngineConfiguration -->
<!-- processEngine Activiti的流程引擎 -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/study_activiti"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="root"/>
<!-- activiti数据库表处理策略:自动更新 -->
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
创建一个测试类,调用 activiti 的工具类,生成acitivti需要的数据库表。
直接使用activiti提供的工具类 ProcessEngines,会默认读取classpath下的activiti.cfg.xml文件,读取其中的数据库配置,创建 ProcessEngine,在创建ProcessEngine 时会自动创建表。
package com.study.test;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;
/**
* @author biandan
* @description
* @signature 让天下没有难写的代码
* @create 2021-06-13 上午 10:39
*/
public class MyTest {
/**
* 生成 Activiti 表
*/
@Test
public void testCreateDbTable() {
//使用classpath下的activiti.cfg.xml中的配置创建processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
}
}
点击运行。大概十几秒,就生成好了表。
Activiti数据表介绍
看到刚才创建的表,我们发现Activiti 的表都以 act_ 开头。
第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。
**ACT_RE** :'RE'表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
**ACT_RU**:'RU'表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
**ACT_HI**:'HI'表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
**ACT_GE** : GE 表示 general。 通用数据, 用于不同场景下
Activiti类关系图
在新版本中,我们通过实验可以发现IdentityService,FormService两个Serivce都已经删除了。所以后面我们对于这两个Service也不讲解了,但老版本中还是有这两个Service
Activiti 入门
BPM(Business Process Management),即业务流程管理,是一种规范化的构造端到端的业务流程,以持续的提高组织业务效率。
用一个圆圈表示,它是流程中运行过程中发生的事情。
活动用圆角矩形表示,一个流程由一个活动或多个活动组成:
详细说明:
1、事件 Event
2、活动 Activity
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:
3、网关 GateWay
网关用来处理决策,有几种常用网关需要了解:
排他网关 (x)
只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流;
如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引擎会抛出异常。
排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。
并行网关 (+)
可以同时执行多条线路,也可以在网关上设置条件
拆分 —— 计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行
合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
事件网关 (+)
专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
4、流向 Flow
流是连接两个流程节点的连线。常见的流向包含以下几种:
5、流程设计器使用
Palette(画板)
Connection—连接
Event---事件
Task---任务
Gateway---网关
Container—容器
Boundary event—边界事件
Intermediate event- -中间事件
流程图设计完毕保存生成.bpmn文件
首先选中存放图形的目录(选择resources下的bpmn目录),点击菜单:New -> BpmnFile
我们创建 apply.bpmn 请假流程。最左侧是配置区域,中间区域是绘图区,右侧区域是palette画板区域。可以拖动图标。
点击空白区域,指定流程定义的 key,我们这里叫做:myApply。然后每个节点都设置负责人。
创建请假申请,Assignee 输入负责人 zhangsan
然后主管审批负责人,lisi
然后经理审批负责人,wangwu
最后人资审批负责人,zhuliu。然后就结束了
然后使用直线把这个流程串联起来。(点中中心的点,拉动)
效果图:
然后,我们把这个画好的 bpmn 文件复制到桌面,使用记事本打开,其实就是 xml 文件,完整代码如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" 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" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1623556442463" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="myApply" isClosed="false" isExecutable="true" name="请假流程" processType="None">
<startEvent id="_2" name="请假申请流程"/>
<userTask activiti:assignee="zhangsan" activiti:exclusive="true" id="_3" name="创建请假申请"/>
<userTask activiti:assignee="lisi" activiti:exclusive="true" id="_4" name="主管审批"/>
<userTask activiti:assignee="wangwu" activiti:exclusive="true" id="_5" name="经理审批"/>
<userTask activiti:assignee="zhuliu" activiti:exclusive="true" id="_6" name="人资审批"/>
<endEvent id="_7" name="请假结束流程"/>
<sequenceFlow id="_8" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_9" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_10" sourceRef="_4" targetRef="_5"/>
<sequenceFlow id="_11" sourceRef="_5" targetRef="_6"/>
<sequenceFlow id="_12" sourceRef="_6" targetRef="_7"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myApply">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="165.0" y="25.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="140.0" y="100.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="55.0" width="85.0" x="135.0" y="185.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="135.0" y="285.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_6" id="Shape-_6">
<omgdc:Bounds height="55.0" width="85.0" x="135.0" y="375.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7">
<omgdc:Bounds height="32.0" width="32.0" x="160.0" y="460.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_6" targetElement="_7">
<omgdi:waypoint x="176.0" y="430.0"/>
<omgdi:waypoint x="176.0" y="460.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="181.0" y="57.0"/>
<omgdi:waypoint x="181.0" y="100.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="_3" targetElement="_4">
<omgdi:waypoint x="180.0" y="155.0"/>
<omgdi:waypoint x="180.0" y="185.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_11" id="BPMNEdge__11" sourceElement="_5" targetElement="_6">
<omgdi:waypoint x="177.5" y="340.0"/>
<omgdi:waypoint x="177.5" y="375.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_10" id="BPMNEdge__10" sourceElement="_4" targetElement="_5">
<omgdi:waypoint x="177.5" y="240.0"/>
<omgdi:waypoint x="177.5" y="285.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>