环境信息:
IntelliJ IDEA 2021.3.3 (Ultimate Edition)
JDK 1.8
工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种**预定义的规则(约定·好的)**自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。通俗来讲,就是业务上一个完整的审批流程。 例如:员工的请假,出差,外出采购,合同审核等等,这些过程,都是一个工作流。
对于工作流的处理,如果采用原始的方式,我们需要拿着各种文件到各个负责人那里去签字,需要在多个部门之间不断审批,这种方式费时费力。而我们可以借助软件系统来协助我们处理这些审批流程,这样就出现了工作流系统,使用工作流系统后可以极大的提高工作效率。
工作流模型:
填写请假单 -> 部门经理审批 -> 总经理审批 -> 人事备案
要实现上述的流程,我们自己可以通过字段标识来实现这个审批效果,在业务表中加个字段,比如填写请假单用1标识,部门经理用2标识,总经理用3标识,人事备案用4标识,好像看起来没啥问题,也实现了审批效果。可是一旦我们的流程出现变化,这个时候我们就需要改动我们的代码,这显然是不可取的,需要用专业的方式来实现工作流的管理,并且可以做到业务流程变化之后,我们的程序可以不用改变,流程自动完成。如果可以实现这样的效果,那么我们的业务系统的适应能力就得到了极大提升。在这样的背景下,就出现了工作流引擎。
为什么使用工作流引擎,能实现业务流程改变,不用修改代码,流程还能自动推进?
(1)我们先来说说为什么流程改变,不用修改代码:我们的工作流引擎都实现了一个规范,这个规范要求我们的流程管理与状态字段无关,始终都是读取业务流程图的下一个节点。当业务更新的时候我们只需要更新业务流程图就行了。这就实现了业务流程改变,不用修改代码。
(2)再来说说流程自动推进,这个原理就更简单了,就拿上面的请假模型来说,工作流引擎会用一张数据库表来记录当前处在的节点。当填写完请假单后肯定是要轮到部门经理来审批了,所以我们一旦完成了请假单填写那么这条记录将会被从这张表删除掉,并且会把下一个节点部门经理的信息插入到这张表中,当我们用部门经理的信息去这张表中查询的时候就能查出部门经理相关的审批的信息了,以此类推,这样层层递进,就实现了流程的自动递交了。
主流框架有:Activiti
、JBPM
、Camunda
、Flowable
,国产的盘古BPM
、云程
Activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN进行定义,业务流程按照预先定义的流程进行执行。实现了系统的流程由Activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作流量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
官方网站:https://www.activiti.org
BPM(Business Process Management)即业务流程管理,是一种规范化的构造端到端的业务流程,以持续提高组织业务效率。
BPM 软件就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整理及调整的经营方法与解决方案的 IT 工具。使用 BPM 软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,可以降低企业成本,提高利润。
BPMN(Business Process Model And Notation)即业务流程模型和符号,是一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程。Activit 就是使用 BPMN 进行流程建模、流程执行管理的。
BPMN2.0 是业务流程建模符号 2.0 的缩写,它由 Business Process Management Initiative 这个非营利协会创建并不断发展。BPMN2.0 是使用一些符号来明确业务流程设计流程图的一套符号规范,能增进业务建模时的沟通效率。目前 BPMN2.0 是最新的版本,它用于在 BPM 上下文中进行布局和可视化的沟通。
BPMN2.0 的基本符号主要包含:
事件 Event
开始:表示一个流程的开始
中间:发生的开始和结束事件之间,影响处理的流程
结束:表示该过程结束
活动 Activities
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程;其次,你还可以为活动指定不同的类型。常见活动如下:
网关 GateWay
用于表示流程的分支与合并,有几种常用网关需要了解:
排他网关: 只有一条路径会被选择
并(平)行网关: 所有路径会被同时选择。
包容网关: 可以同时执行多条线路,也可以在网关上设置条件。
事件网关: 专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
流向 Flow
流是连接两个流程节点的连线,常见的流向包含以下几种:
顺序流:用一个带实心箭头的实心线表示,用于指定活动执行的顺序
信息流:用一条带箭头的虚线表示,用于描述两个独立的业务参与者(业务实体/业务角色)之间发送和接受的消息流动
关联:用一根带有线箭头的点线表示,用于将相关的数据、文本和其他人工信息与流对象联系起来。用于展示活动的输入和输出
第一步: 引入依赖并初始化数据库
既然activiti是一个框架,那么我们肯定是需要引入对应的jar包坐标的,具体参考代码中的。
第二步: 通过工具绘画流程图
使用 activiti 流程建模工具(activity-designer)定义业务流程(.bpmn 文件
)
.bpmn
文件就是业务流程定义文件,通过 xml
定义业务流程。
第三步:流程定义部署;
向 activiti 部署业务流程定义(.bpmn 文件),使用 activiti 提供的 api 向 activiti 中部署.bpmn 文件,
通俗来讲,就是让activiti认识要使用的流程
第四步: 启动一个流程实例(Process Instance)
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个java类,实例化两个对象一样,部署的流程就好比java类,启动一个流程实例就好比new一个java对象
第五步: 用户查询待办任务(Task)
因为现在系统的业务流程已经交给 activiti 管理,通过 activiti 就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些 activiti帮我们管理了。实际上我们学习activiti也只是学习它的API怎么使用,因为很多功能activiti都已经封装好了,我们会调用就行了
第六步: 用户办理任务
用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如请假单创建后由部门经理审核,这个过程也是由 activiti 帮我们完成了,不需要我们在代码中硬编码指定下一个任务办理人了
第七步: 流程结束
当任务办理完成没有下一个任务节点了,这个流程实例就完成了。
IDEA版本小于等于2019,可使用Activiti插件actiBPM,大于该版本的IDEA可使用Activiti BPMN visualizer插件绘制流程设计。现使用:Activiti Modeler
Activiti Modeler 是 Activiti 官方提供的一款在线流程设计的前端插件,开发人员可以方便在线进行流程设计,保存流程模型,部署至流程定义等等,后续我们的项目也是集成Activiti Modeler绘制流程定义。
官网下载:https://www.activiti.org/get-started
解压activiti-5.22.0.zip
,在activiti-5.22.0\wars
目录下获取 activiti-explorer.war
将activiti-explorer.war
放到 tomcat部署目录(Tomcat目录下\webapps
),启动 tomcat:双击 apache-tomcat的安装目录\bin\startup.bat
http://localhost:8080/activiti-explorer
默认登录账号:kermit
kermit
上面有很多功能,我们关注流程设计即可,如下图:
点击上图:流程
--> 新建模型
--> 输入模型名称(请假)
–> 创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jjgZDKBx-1686055679452)(./doc/photo/15后.PNG)]
Activiti项目是基于Apache License许可的开源项目,项目前主管是 Tom Baeyens。Activiti项目前身是JBPM,因此Activiti初始版本是5,Activiti项目支持BPMN标准。
与Spring Boot更好的原生支持,旧版需要配置activiti.cfg.xml
文件,还需要手动加载,新版本只需配置 yml
文件即可
引入SpringSecurity作为默认用户与角色的默认安全机制(相比旧版自己配置有点强硬)
核心API进行了封装,封装了不少如none值
等判断,让开发者更关注业务流程即可,
对云发布,分布式支持等,去掉了用户和表单两个接口类,需要自己手动添加。
取消了FormService
和 IdentityService
模块,将原先RepositorySertvice
,RuntimeService
,ManagementService
,TaskService
,HistoryService
五个模块合并成ProcessRuntime
,TaskRuntime
两个模块
我们定义一个请假流程
指定标签名称:张三审批,节点任务负责人:zhangsan
下载文件为:qingjia.bpmn20.xml
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 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/processdef">
<process id="qingjia" isExecutable="true">
<startEvent id="sid-1AB654AE-D3A2-4240-82A7-F8D959B29A9A">startEvent>
<userTask id="sid-0DAD9B06-6F38-4616-BC16-156989CB12BA" name="张三审批" activiti:assignee="zhangsan">userTask>
<sequenceFlow id="sid-581CCDA2-D9E0-4C5B-83AE-93F409832ED4" sourceRef="sid-1AB654AE-D3A2-4240-82A7-F8D959B29A9A" targetRef="sid-0DAD9B06-6F38-4616-BC16-156989CB12BA">sequenceFlow>
<endEvent id="sid-E210AB99-58D8-43D0-B24A-CFF3E85A9512">endEvent>
<userTask id="sid-E9C4E51D-9F58-4C99-B1A8-E258AD244426" name="李四" activiti:assignee="lisi">userTask>
<sequenceFlow id="sid-9978CDC0-34F4-40ED-8A7C-80E907514297" sourceRef="sid-0DAD9B06-6F38-4616-BC16-156989CB12BA" targetRef="sid-E9C4E51D-9F58-4C99-B1A8-E258AD244426">sequenceFlow>
<sequenceFlow id="sid-DE57A74C-09D5-42FE-BE85-A0EAE8CC89EF" sourceRef="sid-E9C4E51D-9F58-4C99-B1A8-E258AD244426" targetRef="sid-E210AB99-58D8-43D0-B24A-CFF3E85A9512">sequenceFlow>
process>
<bpmndi:BPMNDiagram id="BPMNDiagram_qingjia">
<bpmndi:BPMNPlane bpmnElement="qingjia" id="BPMNPlane_qingjia">
<bpmndi:BPMNShape bpmnElement="sid-1AB654AE-D3A2-4240-82A7-F8D959B29A9A" id="BPMNShape_sid-1AB654AE-D3A2-4240-82A7-F8D959B29A9A">
<omgdc:Bounds height="30.0" width="30.0" x="136.19997740686003" y="107.99999655783185">omgdc:Bounds>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-0DAD9B06-6F38-4616-BC16-156989CB12BA" id="BPMNShape_sid-0DAD9B06-6F38-4616-BC16-156989CB12BA">
<omgdc:Bounds height="79.99999999999999" width="100.0" x="224.99999329447763" y="82.99999532103554">omgdc:Bounds>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E210AB99-58D8-43D0-B24A-CFF3E85A9512" id="BPMNShape_sid-E210AB99-58D8-43D0-B24A-CFF3E85A9512">
<omgdc:Bounds height="28.00000000000007" width="28.0" x="539.9999919533731" y="108.99999493360532">omgdc:Bounds>
bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E9C4E51D-9F58-4C99-B1A8-E258AD244426" id="BPMNShape_sid-E9C4E51D-9F58-4C99-B1A8-E258AD244426">
<omgdc:Bounds height="80.00000000000001" width="100.0" x="374.9999944120646" y="82.99999369680909">omgdc:Bounds>
bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-581CCDA2-D9E0-4C5B-83AE-93F409832ED4" id="BPMNEdge_sid-581CCDA2-D9E0-4C5B-83AE-93F409832ED4">
<omgdi:waypoint x="166.19997740686003" y="122.9999964079777">omgdi:waypoint>
<omgdi:waypoint x="224.99999329447763" y="122.99999582054932">omgdi:waypoint>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-9978CDC0-34F4-40ED-8A7C-80E907514297" id="BPMNEdge_sid-9978CDC0-34F4-40ED-8A7C-80E907514297">
<omgdi:waypoint x="324.99999329447763" y="122.99999477962673">omgdi:waypoint>
<omgdi:waypoint x="374.9999944120646" y="122.99999423821791">omgdi:waypoint>
bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-DE57A74C-09D5-42FE-BE85-A0EAE8CC89EF" id="BPMNEdge_sid-DE57A74C-09D5-42FE-BE85-A0EAE8CC89EF">
<omgdi:waypoint x="474.9999944120646" y="122.9999941761875">omgdi:waypoint>
<omgdi:waypoint x="539.9999919533731" y="122.99999479937941">omgdi:waypoint>
bpmndi:BPMNEdge>
bpmndi:BPMNPlane>
bpmndi:BPMNDiagram>
definitions>
单击右键上图图片,图片另存为:qingjia.PNG
一般我们将在项目resources下新建BPMN资源文件夹,将qingjia.bpmn20.xml与qingjia.PNG放入BPMN文件夹中:
选择Spring相关插件:
server:
port: 8080 # 端口号
servlet:
context-path: / # 访问起始路径
spring:
datasource: # 数据库连接
username: root # 用户名
password: root # 密码
url: jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&nullCatalogMeansCurrent=true
driver-class-name: com.mysql.cj.jdbc.Driver # 驱动
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.test.activitigroupId>
<artifactId>activitiartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>activitiname>
<description>Demo project for Spring Bootdescription>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-spring-boot-starterartifactId>
<version>7.1.0.M4version>
dependency>
<dependency>
<groupId>org.activiti.dependenciesgroupId>
<artifactId>activiti-dependenciesartifactId>
<version>7.1.0.M4version>
<type>pomtype>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
project>
创建HelloController.java
package com.test.activiti.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping(value = "hello",method = RequestMethod.GET) //Get 浏览器传参 Post 提交表单方式传参
public String hello(){
return "Activiti7 学习";
}
}
启动后访问:
https://activiti.gitbook.io/activiti-7-developers-guide/getting-started/getting-started-activiti-core
当然也可以阿里云查询:
https://developer.aliyun.com/mvn/search
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-spring-boot-starterartifactId>
<version>7.1.0.M4version>
dependency>
<dependency>
<groupId>org.activiti.dependenciesgroupId>
<artifactId>activiti-dependenciesartifactId>
<version>7.1.0.M4version>
<type>pomtype>
dependency>
注意:M5版本以上,会出现每次启动数据库后新生成一条默认数据的BUG,M4版本就不会。
# application.yml
server:
port: 8080 # 端口号
servlet:
context-path: / # 访问起始路径
spring:
datasource: # 数据库连接
username: root # 用户名
password: root # 密码
url: jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&nullCatalogMeansCurrent=true
driver-class-name: com.mysql.cj.jdbc.Driver # 驱动
# activiti7 历史表创建
spring.activiti.history-level: full # 完整存储
spring.activiti.db-history-used: true # 开启历史表存储
# 自动部署 (关闭)
spring.activiti.check-process-definitions: false
如果是Activiti7以前的版本会需要另写一个activiti.cfg.xml
配置文件,启动项目时需要启动。当然activiti 7 也是支持这样的,但是已经能够适配SpringBoot配置文件,这样配置方式是没必要的。
Activiti 7 之前版本:https://www.activiti.org/userguide/
Activiti 的运行支持必须要有这 25 张表的支持,主要是在业务流程运行过程中,记录参与流程的用户主体,用户组信息,以及流程的定义,流程执行时的信息,和流程的历史信息等等。
观察创建的表,我们发现 Activiti 的表都以 act_ 开头,紧接着是表示表的用途的两个字母标识,也和 Activiti 所提供的服务的 API 对应:
表分类 | 表名 | 解释 |
---|---|---|
一般数据 | ||
[ACT_EVT_LOG] | 事件处理日志 | |
[ACT_GE_BYTEARRAY] | 通用的流程定义和流程资源 (二进制表) | |
[ACT_GE_PROPERTY] | 系统相关属性 (存BPMN的二进制表) | |
流程历史记录 | ||
[ACT_HI_ACTINST] | 历史节点 | |
[ACT_HI_ATTACHMENT] | 历史的流程附件 | |
[ACT_HI_COMMENT] | 历史的说明性信息 | |
[ACT_HI_DETAIL] | 历史的流程运行中的细节信息 | |
[ACT_HI_IDENTITYLINK] | 历史的流程运行过程中用户关系 | |
[ACT_HI_PROCINST] | 历史的流程实例 | |
[ACT_HI_TASKINST] | 历史的任务实例 | |
[ACT_HI_VARINST] | 历史的流程运行中的变量信息 | |
流程定义表 | ||
[ACT_RE_DEPLOYMENT] | 部署单元信息 (部署表) | |
[ACT_RE_MODEL] | 模型信息 | |
[ACT_RE_PROCDEF] | 已部署的流程定义 | |
运行实例表 | ||
[ACT_RU_EVENT_SUBSCR] | 运行时事件 | |
[ACT_RU_EXECUTION] | 运行时流程执行实例 | |
[ACT_RU_IDENTITYLINK] | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
[ACT_RU_JOB] | 运行时作业 | |
[ACT_RU_TASK] | 运行时任务 | |
[ACT_RU_VARIABLE] | 运行时变量表 |
Deployment:添加资源文件、获取部署信息、部署时间
package com.test.activiti;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.InputStream;
import java.util.List;
@SpringBootTest
public class Part1_Deployment {
@Autowired
private RepositoryService repositoryService;
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN() {
String filename = "BPMN/diagram.bpmn"; //resources 目录下
//String pngname = "BPMN/bpmn.PNG";
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
//.addClasspathResource(pngname)//图片 原来需要上传bpmnxml文件的同时上传一张相应行的图片用来展现
.name("流程部署测试BPMN") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
}
注意:deployment
选择org.activiti.engine.repository
包:
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN() {
String pngname = "BPMN/bpmn.PNG";//resources 目录下
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(pngname)//图片 原来需要上传bpmnxml文件的同时上传一张相应行的图片用来展现
.name("流程部署测试BPMN") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
/**
* 通过zip部署流程
*/
@Test
public void initDemploymentZIP(){
InputStream fileInputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("BPMN/bpmn.zip");
ZipInputStream zip = new ZipInputStream(fileInputStream);
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zip)
.name("流程部署测试zip")//zip中存在xml和jpg文件,存入数据库会自动解压生成两条数据
.deploy();
System.out.println(deployment.getName());
}
之前:
可以看到zip
文件上传,压缩文件被解压缩了。
/**
* 查询流程部署
*/
@Test
public void getDeployments(){
List<Deployment> list =repositoryService.createDeploymentQuery().list();
for (Deployment deployment :list){
System.out.println("Id:"+deployment.getId());
System.out.println("Name:"+deployment.getName());
System.out.println("Time:"+deployment.getDeploymentTime());
System.out.println("Key:"+deployment.getKey());
}
}
全部代码:
package com.test.activiti;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.InputStream;
import java.util.List;
import java.util.zip.ZipInputStream;
@SpringBootTest
public class Part1_Deployment {
@Autowired
private RepositoryService repositoryService;
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN() {
String filename = "BPMN/diagram.bpmn"; //resources 目录下
//String pngname = "BPMN/bpmn.PNG";
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
//.addClasspathResource(pngname)//图片 原来需要上传bpmnxml文件的同时上传一张相应行的图片用来展现
.name("流程部署测试BPMN") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
/**
* 通过zip部署流程
*/
@Test
public void initDemploymentZIP(){
InputStream fileInputStream = this.getClass()
.getClassLoader()
.getResourceAsStream("BPMN/bpmn.zip");
ZipInputStream zip = new ZipInputStream(fileInputStream);
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zip)
.name("流程部署测试zip")//zip中存在xml和jpg文件,存入数据库会自动解压生成两条数据
.deploy();
System.out.println(deployment.getName());
}
/**
* 查询流程部署
*/
@Test
public void getDeployments(){
List list =repositoryService.createDeploymentQuery().list();
for (Deployment deployment :list){
System.out.println("Id:"+deployment.getId());
System.out.println("Name:"+deployment.getName());
System.out.println("Time:"+deployment.getDeploymentTime());
System.out.println("Key:"+deployment.getKey());
}
}
}
流程部署
Deployment:添加资源文件、获取部署信息、部署时间
流程定义
ProcessDefinition:获取版本号、key、资源名称、部署ID等
key不变 就是同一个流程,多加v2 v3的版本
package com.test.activiti;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part2_ProcessDefinition {
@Autowired
private RepositoryService repositoryService;
/**
* 查询流程定义
*/
@Test
public void getDefinitions(){
// ProcessDefinition 是 org.activiti.engine.repository.ProcessDefinition 这个类
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.list();
for (ProcessDefinition pd: list){
System.out.println("---------流程定义-----------");
System.out.println("Name:"+pd.getName());
System.out.println("Key:"+pd.getKey());
System.out.println("ResourceName:"+pd.getResourceName());
System.out.println("DeploymentId:"+pd.getDeploymentId());
System.out.println("Version:"+pd.getVersion());
System.out.println("===========================");
}
}
@Test
public void delDefinition(){
String pdID = "af658828-c940-11ed-9559-005056c00008";
repositoryService.deleteDeployment(pdID,true); //true 删除下列 所有的历史, false会保留历史任务,一般真实情况下都是false要留痕迹
System.out.println("删除流程定义成功!");
}
}
要删除的:
流程定义 ProcessDefinition 与 流程实例 ProcessInstance 是一对多关系, 一个流程定义能变成多个流程实例
理解为:行动计划与具体行动的关系。
流程定义 ProcessDefinition 相当于活动方案
流程实例 ProcessInstance 每一次执行这个方案的实际行动
package com.test.activiti;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Part3_ProcessInstance {
@Autowired
private RuntimeService runtimeService;
//初始化流程实例列表
@Test
public void initProcessInstance(){
//1、获取页面表单填报的内容,请假时间,请假事由,String fromData
//2、fromData 写入业务表,返回业务表主键ID==businessKey
//3、把业务数据与Acitivi7流程数据关联
//4、可以执行多次该方法
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess_claim","claim");
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
}
}
注意:ProcessInstance
使用 org.activiti.engine.runtime.ProcessInstance
包
数据库 增加
act_ru_execution 两条,因为是两个节点,(不包括结束节点)
act_ru_identitylink 运行市里人员
//获取流程实例列表
@Test
public void getProcessInstance(){
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
for (ProcessInstance pi: list) {
System.out.println("--------流程实例--------");
System.out.println("ProcessInstanceId:"+pi.getProcessInstanceId());//流程实例id uuid
System.out.println("ProcessDefinitionId:"+pi.getProcessDefinitionId()); //流程定义id:processDefinitionKey + 版本号 + uuid
System.out.println("执行结束isEnded:"+pi.isEnded());
System.out.println("是否挂起流程实例isSuspended:"+pi.isSuspended());
}
}
//暂停流程实例
@Test
public void stopProcessInstances(){
runtimeService.suspendProcessInstanceById("8f229d56-ca09-11ed-8386-005056c00008");//流程实例id
System.out.println("挂起流程实例");
}
//激活流程实例
@Test
public void activitieProcessInstances(){
runtimeService.activateProcessInstanceById("8f229d56-ca09-11ed-8386-005056c00008");//流程实例id
System.out.println("激活流程实例");
}
//删除流程实例
@Test
public void delProcessInstances(){
runtimeService.deleteProcessInstance("8f229d56-ca09-11ed-8386-005056c00008","删除原因:删除测试");//流程实例id 删除原因deleteReason
System.out.println("删除流程实例");
}
package com.test.activiti;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part3_ProcessInstance {
@Autowired
private RuntimeService runtimeService;
//初始化流程实例列表
@Test
public void initProcessInstance(){
//1、获取页面表单填报的内容,请假时间,请假事由,String fromData
//2、fromData 写入业务表,返回业务表主键ID==businessKey
//3、把业务数据与Acitivi7流程数据关联
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess_claim","claim");
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
}
//获取流程实例列表
@Test
public void getProcessInstance(){
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
for (ProcessInstance pi: list) {
System.out.println("--------流程实例--------");
System.out.println("ProcessInstanceId:"+pi.getProcessInstanceId());//流程实例id uuid
System.out.println("ProcessDefinitionId:"+pi.getProcessDefinitionId()); //流程定义id:processDefinitionKey + 版本号 + uuid
System.out.println("执行结束isEnded:"+pi.isEnded());
System.out.println("是否挂起流程实例isSuspended:"+pi.isSuspended());
}
}
//暂停流程实例
@Test
public void stopProcessInstances(){
runtimeService.suspendProcessInstanceById("8f229d56-ca09-11ed-8386-005056c00008");//流程实例id
System.out.println("挂起流程实例");
}
//激活流程实例
@Test
public void activitieProcessInstances(){
runtimeService.activateProcessInstanceById("8f229d56-ca09-11ed-8386-005056c00008");//流程实例id
System.out.println("激活流程实例");
}
//删除流程实例
@Test
public void delProcessInstances(){
runtimeService.deleteProcessInstance("8f229d56-ca09-11ed-8386-005056c00008","删除原因:删除测试");//流程实例id 删除原因deleteReason
System.out.println("删除流程实例");
}
}
用户任务属性面板
Assignee: 执行人、代理人
Candidate Users: 候选人(多个用户)
Candidate Groups: 候选组
Due Date: 任务到期时间
新设计工作流:
运行时
ACT_RU_TASK运行时任务节点
package com.test.activiti;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part4_Task {
@Autowired
private TaskService taskService;
//任务查询
@Test
public void getTasks(){
List<Task> list = taskService.createTaskQuery().list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
}
//查询我的待办任务
@Test
public void getTasksByAssignee(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("bajie") //或者填wukong
.list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
//执行任务
@Test
public void completeTask(){ //848df66b-ca1a-11ed-b383-005056c00008 候选人任务
taskService.complete("848df66b-ca1a-11ed-b383-005056c00008");//d74d1e61-ca16-11ed-9636-005056c00008
System.out.println("完成任务");
}
//拾取任务
@Test
public void claimTask(){
// //查询候选人
// List list = taskService.createTaskQuery()
// .taskCandidateUser("bajie") //Activiti6 之前可以 Activiti7不行
// .list();
Task task = taskService.createTaskQuery()
.taskId("848df66b-ca1a-11ed-b383-005056c00008")
.singleResult();
taskService.claim("848df66b-ca1a-11ed-b383-005056c00008","bajie");//任务id taskId 用户id userId
}
//归还任务
@Test
public void setTaskAssignee(){
Task task = taskService.createTaskQuery()
.taskId("848df66b-ca1a-11ed-b383-005056c00008")
.singleResult();
//taskService.setAssignee("848df66b-ca1a-11ed-b383-005056c00008","null"); //归还后候选任务
taskService.setAssignee("848df66b-ca1a-11ed-b383-005056c00008","wukong");//交办 交给已知执行人
}
全部代码:
package com.test.activiti;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part4_Task {
@Autowired
private TaskService taskService;
//任务查询
@Test
public void getTasks(){
List<Task> list = taskService.createTaskQuery().list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
//查询我的待办任务
@Test
public void getTasksByAssignee(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("wukong")
.list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
//执行任务
@Test
public void completeTask(){ //848df66b-ca1a-11ed-b383-005056c00008 候选人任务
taskService.complete("848df66b-ca1a-11ed-b383-005056c00008");//d74d1e61-ca16-11ed-9636-005056c00008
System.out.println("完成任务");
}
//拾取任务
@Test
public void claimTask(){
// //查询候选人
// List list = taskService.createTaskQuery()
// .taskCandidateUser("bajie") //Activiti6 之前可以 Activiti7不行
// .list();
Task task = taskService.createTaskQuery()
.taskId("848df66b-ca1a-11ed-b383-005056c00008")
.singleResult();
taskService.claim("848df66b-ca1a-11ed-b383-005056c00008","bajie");
}
//归还任务
@Test
public void setTaskAssignee(){
Task task = taskService.createTaskQuery()
.taskId("848df66b-ca1a-11ed-b383-005056c00008")
.singleResult();
//taskService.setAssignee("848df66b-ca1a-11ed-b383-005056c00008","null"); //归还后候选任务
taskService.setAssignee("848df66b-ca1a-11ed-b383-005056c00008","wukong");//交办 交给已知执行人
}
}
package com.test.activiti;
import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part5_HistoricTaskInstance {
@Autowired
private HistoryService historyService;
//根据用户名查询历史记录
@Test
public void HistoricTaskInstanceByUser(){
List<HistoricTaskInstance> list = historyService
.createHistoricTaskInstanceQuery()
.orderByHistoricTaskInstanceEndTime()
.asc()//正序 desc 倒叙
.taskAssignee("bajie")
.list();
for (HistoricTaskInstance hi : list){
System.out.println("Id:"+hi.getId());
System.out.println("ProcessInstanceId:"+hi.getProcessInstanceId());
System.out.println("Name:"+hi.getName());
}
}
}
//根据流程实例ID查询历史
@Test
public void HistoricTaskInstanceByProcessInstance(){
List<HistoricTaskInstance> list = historyService
.createHistoricTaskInstanceQuery()
.orderByHistoricTaskInstanceEndTime()
.asc()//正序 desc 倒叙
.processInstanceId("84891467-ca1a-11ed-b383-005056c00008") //审核人任务 这里所有的id在真实场景下都应该是变量
.list();
for (HistoricTaskInstance hi : list){
System.out.println("Id:"+hi.getId());
System.out.println("ProcessInstanceId:"+hi.getProcessInstanceId());
System.out.println("Name:"+hi.getName());
}
}
历史节点
历史流程实例
历史人物实例
历史流程人员:每一个人物环节操作,执行人谁?
EL表达式语言(Expression Language)
UEL统一表达式语言(Unified Expression Languange) 使用activiti时,动态赋值用的
${
开始,以 }
结束,例如 ${day > 100}
${userName == "bajie" and pwd == "123 }
对应activiti数据表
act_ru_variable
运行时参数表
act_hi_varinst
历史参数表 varinst
参数实例缩写
UEL表达式的保留字 | ||
---|---|---|
and | eq | gt |
instanceof | div | or |
le | false | empty |
not | Lt | ge |
UEL表达式的运算符 | |||
---|---|---|---|
运算符 | 功能 | 示例 | 结果 |
+ | 加 | ${1 + 1 } |
2 |
- | div | ${1 - 1 } |
0 |
* | 乘 | ${2 * 2 } |
4 |
/或div | 除 | 2/1或{2 div 1} | 2 |
/或div | 除 | 2/0或{2 div 0} | Infinity |
% 或 mod | 求余 | {3%2}或{3mod2} | 1 |
% 或 mod | 求余 | {3%0}或{3mod0} | 异常 java.lang.ArithmenticException:/by zero |
package com.test.activiti;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
public class Part6_UEL {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
//任务 流程实例启动环节 任务完成 任意时刻
//启动流程实例带参数,执行执行人
@Test
public void initProcessInstanceWithArgs() {
//1、获取页面表单填报的内容,请假时间,请假事由,String fromData
//2、fromData 写入业务表,返回业务表主键ID==businessKey
//3、把业务数据与Acitivi7流程数据关联
//4、流程变量
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("ZhiXingRen","wukong");
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("myProcess_claim","claim",variables);
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
}
//完成任务带参数
@Test
public void completeTaskWItchArgs(){
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("pay","101");
taskService.complete("",null);
System.out.println("完成任务");
}
//启动流程实例带参数,使用实体类 //npmn 必须是小写
@Test
public void initProcessInstanceWitchClassArgs(){
UEL_POJO uel_pojo = new UEL_POJO();
uel_pojo.setZhixingren("bajie");
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("uelpojo",uel_pojo);
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(
"myProcess_claim",
"bKey002",
variables);
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
}
//任务完成环节带参数,指定多个候选人
@Test
public void initProcessInstanceWithCandiDateArgs(){
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("houxuanren","wukong,tangseng");
taskService.complete("",null);
System.out.println("完成任务");
}
//直接指定流程变量
@Test
public void otherArgs(){
runtimeService.setVariable("","pay","101");
// runtimeService.setVariables();
// runtimeService.setVariable();
// runtimeService.setVariables();
}
//局部变量
@Test
public void otherLocalArgs(){
runtimeService.setVariableLocal("","pay","101");
// runtimeService.setVariables();
// runtimeService.setVariable();
// runtimeService.setVariables();
}
}
流程部署:
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN2() {
String filename = "BPMN/Part6_UEL_V2.bpmn"; //resources 目录下Part6_UEL_V2.bpmn 文件
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
.name("流程Part6_UEL_V2.bpmn部署") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
初始化实例:
//初始化流程实例列表
@Test
public void initProcessInstance(){
//1、获取页面表单填报的内容,请假时间,请假事由,String fromData
//2、fromData 写入业务表,返回业务表主键ID==businessKey
//3、把业务数据与Acitivi7流程数据关联
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("myProcess_1uel_V2","claim"); // 流程实例的key processDefinitionKey, 业务表单关联 businessKey (暂时没用)
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
}
bajie
存在任务:
//查询我的待办任务
@Test
public void getTasksByAssignee(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("bajie")
.list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
//查询我的待办任务
@Test
public void getTasksByAssignee(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("tangseng")
.list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
完成任务,并将pay
赋值 101
:
//完成任务带参数
@Test
public void completeTaskWItchArgs(){
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("pay","101");
taskService.complete("e8e412fa-ebe6-11ed-be61-005056c00008",variables);
System.out.println("完成任务");
}
此时唐僧存在任务了:
//查询我的待办任务
@Test
public void getTasksByAssignee(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("tangseng")
.list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
声明实体类:
package com.test.activiti;
import lombok.Data;
import java.io.Serializable;
@Data //lombok
public class UEL_POJO implements Serializable {
private String zhixingren;
private String pay;
}
//直接指定流程变量 全局变量
@Test
public void otherArgs(){
runtimeService.setVariable("","pay","101");//流程实例id,参数,参数值
// runtimeService.setVariables();//可设置多个参数
// taskService.setVariable();
// runtimeService.setVariables();// 可赋值多个
}
//局部变量 当前环节设置有用
@Test
public void otherLocalArgs(){
runtimeService.setVariableLocal("","pay","101");//流程实例id,参数,参数值
// runtimeService.setVariables();
// runtimeService.setVariable();
// runtimeService.setVariables();
}
事件,事件大于等于两个
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN2() {
String filename = "BPMN/Part7_1_Parallel.bpmn"; //resources 目录下
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
.name("流程Part7_1_Parallel.bpmn部署") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
//启动流程实例
@Test
public void initProcessInstanceWitchArgs(){
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(
"myProcess_Parallel");
System.out.println("流程示例ID"+processInstance.getProcessDefinitionId());
}6b7330e6-ed49-11ed-a19e-005056c00008
39887e29-ed4b-11ed-8e48-005056c00008
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN2() {
String filename = "BPMN/Part7_2_Exclusive.bpmn"; //resources 目录下
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
.name("流程Part7_2_Exclusive.bpmn部署") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
//启动流程实例
@Test
public void initProcessInstanceWitchArgs(){
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(
"myProcess_Exclusive");
System.out.println("流程示例ID"+processInstance.getProcessDefinitionId());
}8e0074f2-ed4e-11ed-b766-005056c00008
/**
* 通过bpmn部署流程
*/
@Test
public void initDeploymentBPMN2() {
String filename = "BPMN/Part7_4_Inclusive.bpmn"; //resources 目录下
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
.name("流程Part7_4_Inclusive.bpmn部署") //如果数据库遇到多个相同文件,会在之后追加_V2
.deploy();
System.out.println(deployment.getName());
}
//启动流程实例
@Test
public void initProcessInstanceWitchArgs(){
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(
"myProcess_Inclusive");
System.out.println("流程示例ID"+processInstance.getProcessDefinitionId());
}633cd72a-ed63-11ed-8848-005056c00008
97a4f9d6-ed63-11ed-8d87-005056c00008
97a8f17a-ed63-11ed-8d87-005056c00008
//查询我的待办任务
@Test
public void getTasksByAssignee(){
List<Task> list = taskService.createTaskQuery()
.taskAssignee("bajie")
.list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
//完成任务带参数
@Test
public void completeTaskWItchArgs(){
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("day","1");
taskService.complete("97a8f17a-ed63-11ed-8d87-005056c00008",variables);
System.out.println("完成任务");
}
//任务查询
@Test
public void getTasks(){
List<Task> list = taskService.createTaskQuery().list();
for (Task tk : list){
System.out.println("Id:"+tk.getId());
System.out.println("Name:"+tk.getName());
System.out.println("执行人Assignee:"+tk.getAssignee());
}
}
@Test
public void completeTask2(){
//沙僧包含任务:a7958645-ed64-11ed-9b87-005056c00008
//悟空包含任务:a7958647-ed64-11ed-9b87-005056c00008
taskService.complete("a7958645-ed64-11ed-9b87-005056c00008");
taskService.complete("a7958647-ed64-11ed-9b87-005056c00008");
System.out.println("完成任务");
}
/*
* Copyright 2010-2020 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.test.activiti;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import java.util.Collection;
@Component
public class SecurityUtil {
//登陆类 借助spring security
private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
@Autowired
private UserDetailsService userDetailsService;
public void logInAs(String username) { //用username去登录
UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
}
logger.info("> Logged in as: " + username);
SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public Object getCredentials() {
return user.getPassword();
}
@Override
public Object getDetails() {
return user;
}
@Override
public Object getPrincipal() {
return user;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return user.getUsername();
}
}));
org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
}
}
用户创建:(仅用于测试)
/*
* Copyright 2010-2020 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.test.activiti;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
//@Configuration
public class DemoApplicationConfiguration {
private Logger logger = LoggerFactory.getLogger(DemoApplicationConfiguration.class);
//@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"bob", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"bajie", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},//john
{"wukong", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},//hannah
{"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"system", "password", "ROLE_ACTIVITI_USER"},
{"admin", "password", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = asList(Arrays.copyOfRange(user, 2, user.length));
logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), //创建用户密码 创建用户 创建项目 创建用户
authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
写代码:
package com.test.activiti;
import org.activiti.api.model.shared.model.VariableInstance;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.authority.AuthorityUtils;
import java.util.List;
@SpringBootTest
public class Part8_ProcessRuntime {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private SecurityUtil securityUtil;
}
//获取流程实例
@Test
public void getProcessInstance() {
securityUtil.logInAs("bajie");
Page<ProcessInstance> processInstancePage = processRuntime
.processInstances(Pageable.of(0, 100));//分页查询
System.out.println("流程实例数量:" + processInstancePage.getTotalItems());
List<ProcessInstance> list = processInstancePage.getContent();
for (ProcessInstance pi : list) {
System.out.println("Id:" + pi.getId());
System.out.println("Name:" + pi.getName());
System.out.println("StartDate:" + pi.getStartDate());
System.out.println("Status:" + pi.getStatus());
System.out.println("ProcessDefinitionId:" + pi.getProcessDefinitionId()); //流程定义Id
System.out.println("ProcessDefinitionKey:" + pi.getProcessDefinitionKey());//流程定义key
}
}
7 engine下,5和6都是api下
//启动流程实例
@Test
public void startProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start().withProcessDefinitionKey("myProcess_ProcessRuntime")
.withName("第一个流程实例名称")
//.withVariable("","")
.withBusinessKey("自定义Key")
.build()
);
}
//获取流程实例
@Test
public void getProcessInstance() {
securityUtil.logInAs("bajie");
Page<ProcessInstance> processInstancePage = processRuntime
.processInstances(Pageable.of(0, 100));//分页查询
System.out.println("流程实例数量:" + processInstancePage.getTotalItems());
List<ProcessInstance> list = processInstancePage.getContent();
for (ProcessInstance pi : list) {
System.out.println("Id:" + pi.getId());
System.out.println("Name:" + pi.getName());
System.out.println("StartDate:" + pi.getStartDate());
System.out.println("Status:" + pi.getStatus());
System.out.println("ProcessDefinitionId:" + pi.getProcessDefinitionId()); //流程定义Id
System.out.println("ProcessDefinitionKey:" + pi.getProcessDefinitionKey());//流程定义key
}
}
//删除流程实例
@Test
public void delProcessINstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.delete(ProcessPayloadBuilder
.delete()
.withProcessInstanceId("31a677d2-ed6b-11ed-b88c-005056c00008")
.build());
}
//挂起流程示例
@Test
public void suspendProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.suspend(ProcessPayloadBuilder
.suspend()
.withProcessInstanceId("129663bb-ebe4-11ed-afeb-005056c00008")
.build());
}
之前
//激活流程实例
@Test
public void resumeProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.resume(ProcessPayloadBuilder
.resume()
.withProcessInstanceId("129663bb-ebe4-11ed-afeb-005056c00008")
.build());
}
//流程实例参数
@Test
public void getVariable(){
securityUtil.logInAs("bajie");
List<VariableInstance> list = processRuntime.variables(ProcessPayloadBuilder
.variables()
.withProcessInstanceId("a")
.build());
for (VariableInstance vi : list){
System.out.println("-------------");
System.out.println("Name:"+vi.getName());
System.out.println("Value:"+vi.getValue());
System.out.println("TaskId:"+vi.getTaskId());
System.out.println("ProcessInstance:"+vi.getProcessInstanceId());
}
}
排他网关:
TaskRuntime
package com.test.activiti;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part9_TaskRuntime {
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
//获取当前登录用户任务
@Test
public void getTask() {
securityUtil.logInAs("bajie"); //真实情况下没有
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 100));
List<Task> list = tasks.getContent();
for (Task tk : list) {
System.out.println("Id:" + tk.getId());
System.out.println("Name:" + tk.getName());
System.out.println("Status:" + tk.getStatus());
System.out.println("CreatedDate:" + tk.getCreatedDate());
if (tk.getAssignee() == null) {
//候选人为当前登录用户,null的时候需要前端拾取
System.out.println("Assignee: 待拾取任务");
} else {
System.out.println("Assignee:" + tk.getAssignee());
}
}
}
}
/*
* taskRuntime.tasks()源码
*/
public Page<Task> tasks(Pageable pageable) {
String authenticatedUserId = this.securityManager.getAuthenticatedUserId(); //用到了spring security
if (authenticatedUserId != null && !authenticatedUserId.isEmpty()) {
List<String> userGroups = this.userGroupManager.getUserGroups(authenticatedUserId);
return this.tasks(pageable, TaskPayloadBuilder.tasks().withAssignee(authenticatedUserId).withGroups(userGroups).build());
} else {
throw new IllegalStateException("You need an authenticated user to perform a task query");
}
}
//完成任务
@Test
public void completeTask() {
securityUtil.logInAs("bajie"); //真实情况下没有
Task task = taskRuntime.task("3afd8512-ebe3-11ed-92b6-005056c00008");
if (task.getAssignee() == null) {
taskRuntime.claim(TaskPayloadBuilder.claim()
.withTaskId(task.getId())
.build());
}
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.build());
System.out.println("任务执行完成");
}
用户三种来源
知识点
package com.test.activiti.security;
import com.test.activiti.mapper.UserInfoBeanMapper;
import com.test.activiti.pojo.UserInfoBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component //变成一个组件
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoBeanMapper userInfoBeanMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// //写死
// String password = passwordEncoder().encode("111");
//
// return new User(username
// ,password
// ,AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ACTIVITI_USER")); //给用户增加角色
//
//读取数据库判断用户
//如果用户是null抛出异常
//返回用户信息
UserInfoBean userInfoBean = userInfoBeanMapper.selectByUserName(username);
if (userInfoBean == null) {
throw new UsernameNotFoundException("数据库中无此用户");
}
return userInfoBean;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
创建实体类:
package com.test.activiti.pojo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
@Component
public class UserInfoBean implements UserDetails {
private Long id;
private String name;
private String username;
private String password;
private String address;
private String roles;
public String getAddress(){
return address;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.stream(roles.split(","))
.map(s -> new SimpleGrantedAuthority(s))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
mapper:
package com.test.activiti.mapper;
import com.test.activiti.pojo.UserInfoBean;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;
@Mapper
@Component
public interface UserInfoBeanMapper {
@Select("select * from user where username = #{username}")
UserInfoBean selectByUserName(@Param("username")String username);
}
这里因为之前tomcat用了80端口,因此此项目启动用8081端口启用:
package com.test.activiti.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class ActivitiSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private LoginFailHandler loginFailHandler;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.formLogin()
//登陆方法
.loginPage("/login")
.loginProcessingUrl("/login") //登陆验证之后的处理
//登陆后
.successHandler(loginSuccessHandler)
.failureHandler(loginFailHandler)
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.logout()
.permitAll()
.and().csrf().disable()
.headers()
.frameOptions()
.disable();
}
}
登陆成功处理类:
package com.test.activiti.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.activiti.util.AjaxResponse;
import com.test.activiti.util.GlobalConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("loginSuccessHandler")
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
}
@Override //后台从页面拿到什么值 HttpServletRequest 后台向页面输出什么值 HttpServletResponse
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("登陆成功LoginSuccessHandler");
// response.getWriter().write("获取登录名:"+authentication.getName());
// response.getWriter().write(objectMapper.writeValueAsString(AjaxResponse.AjaxData(0,"成功",authentication.getName()))
// );
response.getWriter().write(
objectMapper.writeValueAsString(
AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.SUCCESS.getCode(),
GlobalConfig.ResponseCode.SUCCESS.getDesc(),
authentication.getName())
));
}
}
这里需要使用接口测试工具来进行测试:
测试登录失败:
package com.test.activiti.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.activiti.util.AjaxResponse;
import com.test.activiti.util.GlobalConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class LoginFailHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(500);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
// response.getWriter().write("登陆失败,原因是:"+ exception.getMessage());
response.getWriter().write(
objectMapper.writeValueAsString(
AjaxResponse.AjaxData(
GlobalConfig.ResponseCode.ERROR.getCode(),
GlobalConfig.ResponseCode.ERROR.getDesc(),
"登陆失败,原因是:"+ exception.getMessage())
));
}
}
bpmn:https://bpmn.io/
bpmn download:https://bpmn.io/toolkit/bpmn-js/download/
点击 Explore Examples
i18n可以根据需要修改语言配置
properties-panel可以创建BPMN.js的控制面板
npm install
npm run dev