Spring Boot2.3整合activiti7 实现快速入门
activiti 工作流引擎,主要用于可灵活变动的系统的流程使用,使用activiti 管理自动化流程,摆脱用数据库状态为标识做流程,当流程改变还需要改代码,避免无用工作量
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.onefox</groupId>
<artifactId>activiti</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>activiti</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
activiti:
database-schema-update: true
check-process-definitions: false
db-history-used: true
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconect=true&serverTimezone=GMT%2b8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
filters: start
maxActive: 50
initialSize: 0
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1 from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
removeAbandoned: true
removeAbandonedTimeout: 180
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
Activit核心配置文件activiti.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"/>
</bean>
<!-- Activiti单独运行的ProcessEngine配置 -->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
这时候启动时就可以创建Activiti所需要的25张表了数据库表命名含义:ACT_RE_
:'RE’表示Repository。这个前缀的表包含了流程定义和流程静态资源(图片、规则等等)。
ACT_RU_:'RU’表示Runtime。这些运行时的表,包含流程实例,任务、变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这些运行时表可以一直很小并且速度很快。
ACT_HI_
:'HI’表示History。这些表包含历史数据,比如历史流程实例,变量,任务等等。
ACT_GE_:'GE’表示General。通用数据,用于不同场景下。
/**
RepositoryService: Activiti的资源管理接口,提供了管理和控制流程发布包和流程定义的操作。
查询引擎中的发布包和流程定义。
暂停或激活发布包以及对应全部和特定流程定义。暂停意味着它们不能再在执行任务操作了,激活是对应的反向操作。
获取多种资源,像包含在发布包中的文件获引擎自动生成的流程图。
获取流程定义的POJO,可以用解析流程,而不必通过XML。
RuntimeService: 流程执行接口,获取流程执行的信息
TaskService: 任务接口,获取任务信息
HistoryService: 历史任务接口,获取历史任务的信息,执行流程等等
ManagementService: Activiti的引擎管理接口,不用在业务中,维护使用
*/
bpmn文件代码,工作流引擎保存的格式文件
<?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" xmlns:yaoqiang="http://bpmn.sourceforge.net" exporter="Yaoqiang BPMN Editor" exporterVersion="5.3" expressionLanguage="http://www.w3.org/1999/XPath" id="_1605260257556" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<process id="activitiTest" isClosed="false" isExecutable="true" name="财务审批" processType="None">
<extensionElements>
<yaoqiang:description/>
<yaoqiang:pageFormat height="841.8897637795276" imageableHeight="831.8897637795276" imageableWidth="588.1102362204724" imageableX="5.0" imageableY="5.0" orientation="0" width="598.1102362204724"/>
<yaoqiang:page background="#FFFFFF" horizontalCount="1" verticalCount="1"/>
</extensionElements>
<startEvent id="start" isInterrupting="true" name="开始" parallelMultiple="false">
<outgoing>flow1</outgoing>
<outputSet/>
</startEvent>
<userTask activiti:assignee="张女士" completionQuantity="1" id="task1" implementation="##unspecified" isForCompensation="false" name="填写财务报销单" startQuantity="1">
<incoming>flow1</incoming>
<outgoing>flow2</outgoing>
</userTask>
<sequenceFlow id="flow1" sourceRef="start" targetRef="task1"/>
<userTask activiti:assignee="程经理" completionQuantity="1" id="task2" implementation="##unspecified" isForCompensation="false" name="财务部经理审批" startQuantity="1">
<incoming>flow2</incoming>
<outgoing>flow3</outgoing>
</userTask>
<sequenceFlow id="flow2" sourceRef="task1" targetRef="task2"/>
<userTask activiti:assignee="赵总" completionQuantity="1" id="task3" implementation="##unspecified" isForCompensation="false" name="总经理审批" startQuantity="1">
<incoming>flow3</incoming>
<outgoing>flow4</outgoing>
</userTask>
<sequenceFlow id="flow3" sourceRef="task2" targetRef="task3"/>
<endEvent id="end" name="结束">
<incoming>flow4</incoming>
<inputSet/>
</endEvent>
<sequenceFlow id="flow4" sourceRef="task3" targetRef="end"/>
</process>
<bpmndi:BPMNDiagram id="zgc-activitiTest" name="Untitled Diagram" resolution="96.0">
<bpmndi:BPMNPlane bpmnElement="activitiTest">
<bpmndi:BPMNShape bpmnElement="start" id="Yaoqiang-startevent2">
<omgdc:Bounds height="32.0" width="32.0" x="100.0" y="160.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="28.0" x="102.0" y="199.45"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="task1" id="zgc-task1">
<omgdc:Bounds height="55.0" width="105.0" x="180.0" y="150.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="83.0" x="191.0" y="168.99"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="task2" id="zgc-task2">
<omgdc:Bounds height="55.0" width="105.0" x="330.0" y="150.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="72.0" x="346.5" y="168.99"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="task3" id="zgc-task3">
<omgdc:Bounds height="55.0" width="105.0" x="480.0" y="150.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="61.0" x="502.0" y="168.99"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="zgc-end">
<omgdc:Bounds height="32.0" width="32.0" x="630.0" y="160.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="28.0" x="632.0" y="199.45"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow4" id="zgc-flow4">
<omgdi:waypoint x="585.0" y="177.5"/>
<omgdi:waypoint x="630.0" y="176.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="6.0" x="604.5" y="166.24"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="zgc-flow3">
<omgdi:waypoint x="435.0" y="177.5"/>
<omgdi:waypoint x="480.0" y="177.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="6.0" x="454.5" y="166.99"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="zgc-flow2">
<omgdi:waypoint x="285.0" y="177.5"/>
<omgdi:waypoint x="330.0" y="177.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="6.0" x="304.5" y="166.99"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow1" id="zgc-flow1">
<omgdi:waypoint x="132.0" y="176.0"/>
<omgdi:waypoint x="180.0" y="177.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="21.02" width="6.0" x="153.0" y="166.24"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
工作流常用方法
1. 流程部署
//工作流引擎
private static ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault().buildProcessEngine();
//流程文件名称
private final static String BPMN = "activiti.bpmn";
//流程定义ID
private final static String ID = "activitiTest:1:210004";
//businessKey 绑定业务ID
private final static String BUSINESSKEY = "activiti";
//DeploymentID 部署ID
private final static String DEPLOYMENT = "212501";
/**
* 流程部署
*/
@Test
public void deploy() {
RepositoryService repositoryService = processEngine.getRepositoryService();//资源对象
Deployment deployment = repositoryService.createDeployment().key(BUSINESSKEY).addClasspathResource(BPMN).name("财务审批流程").deploy();
System.out.println("流程ID:" + deployment.getId());
System.out.println("流程名称:" + deployment.getName());
}
执行完就可以看到act_re_procdef,act_re_deployment已经有数据了
act_re_procdef 流程信息
act_re_deployment 部署信息
2:流程启动
/**
* 流程启动
*/
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据key,对应act_re_procdef表的主键
ProcessInstance processInstance = runtimeService.startProcessInstanceById(ID, BUSINESSKEY);
//获取流程实例的相关信息
String processDefinitionId = processInstance.getProcessDefinitionId();
System.out.println("流程定义的id = " + processDefinitionId);
String id = processInstance.getId();
System.out.println("流程执行实例的id = " + id);
3:查询代办任务
/**
* 查询代办任务
*/
@Test
public void task() {
TaskService taskService = processEngine.getTaskService();
/**
* processDefinitionId: act_re_procdef
* taskAssignee taskName
*/
List<Task> taskList = taskService.createTaskQuery().processDefinitionId(ID).taskAssignee("").list();
//遍历任务列表
for (Task task : taskList) {
String processDefinitionId = task.getProcessDefinitionId();
System.out.println("流程定义id = " + processDefinitionId);
String processInstanceId = task.getProcessInstanceId();
System.out.println("流程实例id = " + processInstanceId);
String assignee1 = task.getAssignee();
System.out.println("任务负责人 = " + assignee1);
String id = task.getId();
System.out.println("任务id = " + id);
String name = task.getName();
System.out.println("任务名称 = " + name);
}
}
4:办理任务
/**
* 办理任务
*/
@Test
public void taskHandling() {
List<Task> taskList = processEngine.getTaskService().createTaskQuery().processDefinitionId(ID).taskAssignee("").list();
if (taskList != null) {
for (Task task : taskList) {
System.out.println(task.getName());
//办理任务
processEngine.getTaskService().complete(task.getId());
}
}
}
可以看到在历史节点中能看到我们流程的办理情况
5:查询流程定义
@Test
public void readProcess() {
ProcessDefinitionQuery processDefinitionQuery = processEngine.getRepositoryService().createProcessDefinitionQuery();
ProcessDefinition processDefinition = processDefinitionQuery.processDefinitionId(ID).singleResult();
List<ProcessDefinition> processDefinitionList = processDefinitionQuery.processDefinitionId(ID).orderByProcessDefinitionId().desc().list();
System.out.println(processDefinitionList.get(0).getKey());
System.out.println(processDefinition.getKey());
}
6:删除部署流程
/**
* 删除部署流程与关联数据
* void deleteDeployment(String deploymentId, boolean cascade); 强制删除,不论流程是否结束
* void deleteDeployment(String deploymentId); 流程未结束,无法删除
*/
@Test
public void deleteProcess() {
processEngine.getRepositoryService().deleteDeployment(DEPLOYMENT, true);
}
7:流程资源查询
/**
* 流程资源查询
* bpmn | png 资源文件等等
*/
@Test
public void processResources() throws IOException {
ProcessDefinitionQuery processDefinitionQuery = processEngine.getRepositoryService().createProcessDefinitionQuery();
ProcessDefinition processDefinition = processDefinitionQuery.processDefinitionId(ID).singleResult();
System.out.println("流程名:" + processDefinition.getName());
System.out.println("资源名:" + processDefinition.getResourceName());
System.out.println("图片名:" + processDefinition.getDiagramResourceName());
//取得资源输入流
RepositoryService repositoryService = processEngine.getRepositoryService();
InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
//复制到本地
String path = "D:\\activiti\\" + processDefinition.getResourceName();
File file = new File(path);
if (!file.exists()) {
file.getParentFile().mkdirs();
}
FileCopyUtils.copy(resourceAsStream, new FileOutputStream(path));
}
可以看到我们的流程资源已经复制到本地了
8:历史流程资源
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/14 16:45
* @Created zgc
* @Describe 历史流程资源
*/
@Test
public void readHistory() {
HistoryService historyService = processEngine.getHistoryService();
//历史流程定义资源
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processDefinitionId(ID).singleResult();
System.out.println("历史流程定义实例ID:" + historicProcessInstance.getId());
System.out.println("历史流程定义名称:" + historicProcessInstance.getProcessDefinitionName());
//历史流程用户节点资源
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().list();
int index = 1;
for (HistoricTaskInstance historicTaskInstance : list) {
System.out.println("历史步骤 " + index + " : " + historicTaskInstance.getName() + "==========>节点负责人:" + historicTaskInstance.getAssignee());
index++;
}
HistoricTaskInstance singleResult = historyService.createHistoricTaskInstanceQuery().processDefinitionId(ID).taskAssignee("张三").singleResult();
System.out.println("负责人:" + singleResult.getAssignee() + ",任务名:" + singleResult.getName());
//历史流程完整节点资源
List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery().list();
int index1 = 1;
for (HistoricActivityInstance activityInstance : activityInstanceList) {
System.out.println("历史完整步骤 " + index1 + " : " + activityInstance.getActivityName() + "==========>节点类型:" + activityInstance.getActivityType());
index1++;
}
}
9:流程定义挂起,流程定义恢复
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/14 17:07
* @Created zgc
* @Describe 流程挂起,流程恢复 挂起流程定义
*/
@Test
public void processStopAll() {
ProcessDefinition processDefinition = processEngine.getRepositoryService().createProcessDefinitionQuery().processDefinitionId(ID).singleResult();
/*
流程挂起状态:返回true,反之false
*/
boolean suspended = processDefinition.isSuspended();
if (suspended) {
/*
挂起状态:
defID
级联启动
延迟启动
*/
processEngine.getRepositoryService().activateProcessDefinitionById(ID, true, null);
System.out.println(ID +" 流程恢复");
} else {
/*
正常状态:
defID
级联挂起
延迟挂起
*/
processEngine.getRepositoryService().suspendProcessDefinitionById(ID, true, null);
System.out.println(ID +" 流程挂起");
}
}
10:任务挂起,任务恢复
分割线
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/14 17:07
* @Created zgc
* @Describe 流程挂起,流程恢复 挂起单条任务
*/
@Test
public void processStopOne() {
List<ProcessInstance> processInstanceList = processEngine.getRuntimeService().createProcessInstanceQuery().processDefinitionId(ID).list();
for (ProcessInstance processInstance : processInstanceList) {
/*
流程挂起状态:返回true,反之false
*/
boolean suspended = processInstance.isSuspended();
if (suspended) {
/*
挂起状态:
*/
processEngine.getRuntimeService().activateProcessInstanceById(processInstance.getId());
System.out.println(processInstance.getId() +" 流程恢复");
} else {
/*
正常状态:
*/
processEngine.getRuntimeService().suspendProcessInstanceById(processInstance.getId());
System.out.println(processInstance.getId() +" 流程挂起");
}
}
}
根据流程变量进行任务办理流程
1:财务审批实体类
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
/**
* @Classname Finance
* @Date 2020/11/14 20:50
* @Created zgc
* @Describe 财务审批实体
*/
public class Finance implements Serializable {
private static final long serialVersionUID = -7777387035032541168L;
/**
* 主键
*/
private Integer id;
/**
* 申请人名字
*/
private String name;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 报销金额
*/
private BigDecimal num;
/**
* 报销内容
*/
private String reason;
/**
* 报销类型
*/
private String type;
}
2:流程部署
3:带参数启动
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/14 21:03
* @Created zgc
* @Describe 携带参数启动
*/
@Test
public void startParams() {
RuntimeService runtimeService = processEngine.getRuntimeService();
Finance finance = new Finance();
finance.setId(UUID.randomUUID().toString());
finance.setName("张女士");
finance.setNum(new BigDecimal("23588.00"));
finance.setReason("会所按摩");
finance.setStartTime(new Date());
finance.setType("商务费用");
Map<String, Object> params = new HashMap<>();
params.put("finance",finance);
ProcessInstance processInstance = runtimeService.startProcessInstanceById(ID, BUSINESSKEY,params);
//获取流程实例的相关信息
String processDefinitionId = processInstance.getProcessDefinitionId();
System.out.println("流程定义的id = " + processDefinitionId);
String id = processInstance.getId();
System.out.println("流程执行实例的id = " + id);
}
4:各节点完成任务
@Test
public void task1(){
Task result = processEngine.getTaskService().createTaskQuery().processDefinitionId(ID).taskAssignee("张女士").singleResult();
System.out.println("执行流程实例ID:"+result.getProcessInstanceId() + ",流程名称:" + result.getName() + ",经办人为:" + result.getAssignee());
processEngine.getTaskService().complete(result.getId());
}
@Test
public void task2(){
Task result = processEngine.getTaskService().createTaskQuery().processDefinitionId(ID).taskAssignee("程经理").singleResult();
System.out.println("执行流程实例ID:"+result.getProcessInstanceId() + ",流程名称:" + result.getName() + ",经办人为:" + result.getAssignee());
processEngine.getTaskService().complete(result.getId());
}
@Test
public void task3(){
Task result = processEngine.getTaskService().createTaskQuery().processDefinitionId(ID).taskAssignee("赵总").singleResult();
System.out.println("执行流程实例ID:"+result.getProcessInstanceId() + ",流程名称:" + result.getName() + ",经办人为:" + result.getAssignee());
processEngine.getTaskService().complete(result.getId());
}
三个节点执行后,任务完成,task表清空,历史人物表中记录历史任务信息
分割线
1:设置任务候选人
bpmn文件,多个候选人通过activiti:candidateUsers标签和逗号隔开
<?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" xmlns:yaoqiang="http://bpmn.sourceforge.net" exporter="Yaoqiang BPMN Editor" exporterVersion="5.3" expressionLanguage="http://www.w3.org/1999/XPath" id="_1605260257556" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<process id="activitiTest" name="财务审批">
<startEvent id="start" name="开始" ></startEvent>
<userTask activiti:candidateUsers="张女士,钱女士" id="task1" name="填写财务报销单"></userTask>
<sequenceFlow id="flow1" sourceRef="start" targetRef="task1"/>
<userTask activiti:assignee="程经理" id="task2" name="财务部经理审批"></userTask>
<sequenceFlow id="flow2" sourceRef="task1" targetRef="task2"/>
<userTask activiti:assignee="赵总" id="task3" name="总经理审批"></userTask>
<sequenceFlow id="flow3" sourceRef="task2" targetRef="task3"/>
<endEvent id="end" name="结束"></endEvent>
<sequenceFlow id="flow4" sourceRef="task3" targetRef="end"/>
</process>
</definitions>
当一个节点有多个人的时候,多个候选人都能查出该条任务信息
替换bpmn文件,重新流程部署
2:流程部署
3:流程启动
4:查询候选人任务
taskCandidateUser 通过该参数从之前保存节点中的多个候选人都可以查出当前任务,非当前节点中的人查不到
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/14 21:41
* @Created zgc
* @Describe 查询候选人任务
*/
@Test
public void candidateUsers(){
TaskService taskService = processEngine.getTaskService();
/**
* processDefinitionId: act_re_procdef
* taskCandidateUser 候选人名称
*/
List<Task> taskList = taskService.createTaskQuery().processDefinitionId(ID).taskCandidateUser("钱女士").list();
List<Task> taskList1 = taskService.createTaskQuery().processDefinitionId(ID).taskCandidateUser("张女士").list();
//遍历任务列表
for (Task task : taskList) {
String processDefinitionId = task.getProcessDefinitionId();
System.out.println("流程定义id = " + processDefinitionId);
String processInstanceId = task.getProcessInstanceId();
System.out.println("流程实例id = " + processInstanceId);
String assignee1 = task.getAssignee();
System.out.println("任务负责人 = " + assignee1);
String id = task.getId();
System.out.println("任务id = " + id);
String name = task.getName();
System.out.println("任务名称 = " + name);
}
}
5:拾取候选者任务
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/15 10:19
* @Created zgc
* @Describe 拾取候选人任务,拾取完成后候选任务成为个人任务,其他候选者查询不到该任务
*/
@Test
public void chaimGroupTask() {
String user = "钱女士";
Task result = processEngine.getTaskService().createTaskQuery().processDefinitionId(ID).taskCandidateUser(user).singleResult();
//result is not null 代表当前用户是
if (result != null) {
processEngine.getTaskService().claim(result.getId(), user);
System.out.println(user + "拾取任务成功,任务ID为:"+result.getId());
}
}
5:当候选人拾取任务时,别的候选人已经查询不到该任务信息了,通过查询待办任务来查询当前任务节点的负责人
6:负责人退还|转让 个人任务到组任务中
/**
* @Classname ActivitiApplicationTest
* @Date 2020/11/15 10:42
* @Created zgc
* @Describe 退还个人任务到组任务 | 转让该任务给别的候选人(也可以转让给非候选人,不建议)
*/
@Test
public void returnGroupTask(){
String assignee = "钱女士";
String user = "张女士";
TaskService taskService = processEngine.getTaskService();
Task result = taskService.createTaskQuery().processDefinitionId(ID).taskAssignee(assignee).singleResult();
if (result != null){
taskService.setAssignee(result.getId(),null);
System.out.println(assignee + "退还|转让任务成功,任务ID为:"+result.getId());
}
}
7:办理任务
分割线
使用 SpringBoot-Activti提供的Api
spring-activiti.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 工作流引擎配置Bean -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 使用Spring的事务管理器 -->
<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>
<!-- 资源服务service -->
<bean id="repositoryService" factory-bean="processEngine"
factory-method="getRepositoryService"/>
<!-- 流程运行service -->
<bean id="runtimeService" factory-bean="processEngine"
factory-method="getRuntimeService"/>
<!-- 任务管理service -->
<bean id="taskService" factory-bean="processEngine"
factory-method="getTaskService"/>
<!-- 历史管理service -->
<bean id="historyService" factory-bean="processEngine"
factory-method="getHistoryService"/>
</beans>
spring-activiti.bpmn
稍微改动一点,和之前的做下区分
<?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" xmlns:yaoqiang="http://bpmn.sourceforge.net" exporter="Yaoqiang BPMN Editor" exporterVersion="5.3" expressionLanguage="http://www.w3.org/1999/XPath" id="_1605260257556" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<process id="spring-activitiTest" name="辞职申请">
<startEvent id="start" name="开始" ></startEvent>
<userTask activiti:candidateUsers="张女士,钱女士" id="task1" name="填写离职单"></userTask>
<sequenceFlow id="flow1" sourceRef="start" targetRef="task1"/>
<userTask activiti:assignee="程经理" id="task2" name="技术部经理审批"></userTask>
<sequenceFlow id="flow2" sourceRef="task1" targetRef="task2"/>
<userTask activiti:assignee="赵总" id="task3" name="总经理审批"></userTask>
<sequenceFlow id="flow3" sourceRef="task2" targetRef="task3"/>
<endEvent id="end" name="结束"></endEvent>
<sequenceFlow id="flow4" sourceRef="task3" targetRef="end"/>
</process>
</definitions>
整合SpringSecurity
我是用的是 activiti7.1 M6 版本,强制引用SpringSecurity,无法关闭,除非退到M4版本左右才可关闭使用SpringSecurity
SpringSecurity配置文件,idea 建议开启自动导包
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(SpringSecurityConfig.class);
@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService());
}
@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"张三", "password", "ROLE_ACTIVITI_USER", "GROUP_activiti1"},
{"李四", "password", "ROLE_ACTIVITI_USER", "GROUP_activiti1"},
{"王五", "password", "ROLE_ACTIVITI_USER", "GROUP_activiti1"},
{"赵六", "password", "ROLE_ACTIVITI_USER", "GROUP_activiti2"},
{"admin", "password", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = Arrays.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;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Component
public class UserService {
@Resource
private UserDetailsService userDetailsService;
public void logInAs(String username) {
UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
}
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);
}