不同版本使用方式不一样,案例使用两个版本6.5.0及6.6.0,学习中6.5.0 版本是独立框架(服务单独部署)使用的, 6.6.0与springboot集成,
6.5.0版本如下:
下载flowable:
https://github.com/flowable/flowable-engine/releases
选择6.5.0版本
解压:
database中为所需的表,新建flowable数据库,导入mysql即可:
将war文件夹中的5个jar包复制到tomcat的webapps文件中
flowable-admin:后台管理
flow-idm:用户组权限管理
flow-modeler:流程定义管理
flowable-rest:流程引擎对外提供的API接口
flowable-task:用户任务管理
启动tomcat
修改每个项目的配置文件,将数据库配置修改为自己的地址:
其他均是flowable-default文件。
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/flowable?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Hongkong&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=XXXXX
每个项目的lib中需要加入mysql驱动包。
重新启动tomcat后登录:
http://127.0.0.1:8080/flowable-idm 用户名 admin 密码 test
http://127.0.0.1:8080/flowable-admin 用户名 admin 密码 test
http://127.0.0.1:8080/flowable-modeler 用户名 admin 密码 test
需要先登录idm系统后再登录其他系统,否则会登录失败。
框架中引入
<!-- 工作流flowable架包 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
6.6.0版本如下:
集成flowable模块
6.6.0版本集成到框架中即可:引入flowable-ui及modeler即可
<!-- 工作流flowable架包 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
<!--流程引擎的引用,单例唯一,可以通过它获得所有api的服务对象-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.6.0</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-common</artifactId>
<version>6.6.0</version>
</dependency>
<!-- modeler绘制流程图 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
<version>6.6.0</version>
</dependency>
<!-- idm依赖提供身份认证 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
<version>6.6.0</version>
</dependency>
application配置文件:
server:
port: 8085
spring:
application:
name: flowable
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT&nullCatalogMeansCurrent=true
username: root
password: root
flowable:
#关闭定时任务JOB
async-executor-activate: false
#将databaseSchemaUpdate设置为true。当flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
database-schema-update: true
activity-font-name: '楷体'
#flowable打印sql
logging:
level:
org.flowable.engine.impl.persistence.entity.*: debug
org.flowable.task.service.impl.persistence.entity.*: debug
完整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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>flowable-master</artifactId>
<properties>
<mybatis-plus.version>3.3.2</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.3.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- 工作流flowable架包 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
<!--流程引擎的引用,单例唯一,可以通过它获得所有api的服务对象-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.6.0</version>
</dependency>
<!--flowable-ui-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-common</artifactId>
<version>6.6.0</version>
</dependency>
<!-- modeler绘制流程图 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
<version>6.6.0</version>
</dependency>
<!-- idm依赖提供身份认证 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
<version>6.6.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- thymeleaf模块引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
启动后访问localhost:8085
登录后画流程图:
成果图:
每个节点详情(主键id、参数等信息自定义即可):
新建模型:key自定义即可,用于标识流程。
开始:
申请:
申请流程:
主管审批:
排他网关:
老板审批:
保存后导出:
导出BPMN文件:
建好如下文件:
表说明:
大部分表是支持流程设计器的, 真正流程运行所需的表并不多。
表的功能一般可以通过第二个词语缩写来进行区分。
ACT_RE_*
’RE’表示repository(存储)。RepositoryService接口操作的表。带此前缀的表包含的是静态信息,如流程定义、流程的资源(图片,规则等)。
ACT_RU_*
’RU’表示runtime。这是运行时的表存储着流程变量,用户任务、变量、职责(job)等运行时的数据。flowable只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
ACT_ID_*
’ID’表示identity(组织机构)。这些表包含标识的信息,如用户、用户组等。
一般在正式系统中, 会将这些表用业务系统的组织机构, 角色表进行替换。
ACT_HI_*
’HI’表示history。就是这些表包含着历史的相关数据,如结束的流程实例、变量、任务等。
ACT_GE_*
普通数据,各种情况都使用的数据。
表名 | 表说明 |
---|---|
一般数据对应表 | – |
ACT_GE_BYTEARRAY | 通用的流程定义和流程资源 |
ACT_GE_PROPERTY | 系统相关属性 |
流程历史对应表 | – |
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_ID_BYTEARRAY | 二进制数据表 |
ACT_ID_GROUP | 用户组信息表 |
ACT_ID_INFO | 用户信息详情表 |
ACT_ID_MEMBERSHIP | 人与组关系表 |
ACT_ID_PRIV | 权限表 |
ACT_ID_PRIV_MAPPING | 用户或组权限关系表 |
ACT_ID_PROPERTY | 属性表 |
ACT_ID_TOKEN | 系统登录日志表 |
ACT_ID_USER | 用户表 |
流程定义表 | – |
ACT_RE_MODEL | 模型信息 |
ACT_RE_DEPLOYMENT | 部署单元信息 |
ACT_RE_PROCDEF | 已部署的流程定义 |
运行实例表 | – |
ACT_RU_DEADLETTER_JOB | 正在运行的任务表 |
ACT_RU_EVENT_SUBSCR | 运行时事件 |
ACT_RU_EXECUTION | 运行时流程执行实例 |
ACT_RU_HISTORY_JOB | 历史作业表 |
ACT_RU_IDENTITYLINK | 运行时用户关系信息 |
ACT_RU_JOB | 运行时作业表 |
ACT_RU_SUSPENDED_JOB | 暂停作业表 |
ACT_RU_TASK | 运行时任务表 |
ACT_RU_TIMER_JOB | 定时作业表 |
ACT_RU_VARIABLE | 运行时变量表 |
其他表 | – |
ACT_EVT_LOG | 事件日志表 |
ACT_PROCDEF_INFO | 流程定义信息 |
互斥网关(Exclusive Gateway),又称排他网关,他有且仅有一个有效出口,可以理解为if…else if… else if…else,就和我们平时写代码的一样。
并行网关(Parallel Gateway),他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。
包容性网关(Inclusive Gateway),只要满足条件的出口都会执行,可以理解为 if(…) do, if (…) do, if (…) do,所有的条件判断都是同级别的。
工作流测方法总结:
import com.tt.flowable.FlowerApplication;
import com.tt.flowable.pojo.ResponseBean;
import com.tt.flowable.pojo.leave.leaveInfo;
import org.apache.commons.collections.CollectionUtils;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Attachment;
import org.flowable.engine.task.Comment;
import org.flowable.identitylink.api.history.HistoricIdentityLink;
import org.flowable.idm.api.Group;
import org.flowable.idm.api.User;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
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.*;
import java.util.stream.Collectors;
@SpringBootTest(classes = FlowerApplication.class)
public class flowtest {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Autowired
RepositoryService repositoryService;
/**
* 查询用户
*/
@Test
public void queryUser(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//通过IdentityService完成相关的用户和组的管理
IdentityService identityService = processEngine.getIdentityService();
//使用userId
User u=identityService.createUserQuery().userId("用户01").singleResult();
List<User> list=identityService.createUserQuery().list();
for(User user:list){
System.out.println(user.getId()+";"+user.getFirstName());
}
}
/**
* 新增用户act_id_user
*/
@Test
public void createUser(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//通过IdentityService完成相关的用户和组的管理
IdentityService identityService = processEngine.getIdentityService();
User user = identityService.newUser("0001");
user.setFirstName("QQ");
//user.setLastName("01");
user.setEmail("6666666@qq.com");
identityService.saveUser(user);
}
/**
* 创建用户组 ACT_ID_GROUP
*/
@Test
public void createGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService(); // 创建Group对象并指定相关的信息
Group group = identityService.newGroup("G1");
group.setName("研发部");
group.setType("T1"); // 创建Group对应的表结构数据
identityService.saveGroup(group);
}
/**
* 将用户分配给对应的Group
* ACT_ID_MEMBERSHIP
* 用户和组是一个多对多的关联关联
*/
@Test
public void userGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService(); // 根据组的编号找到对应的Group对象
Group group = identityService.createGroupQuery().groupId("G1").singleResult();//查询用户组
List<User> list = identityService.createUserQuery().list();
for (User user : list) { // 将用户分配给对应的组
if("用".equals(user.getFirstName())){
identityService.createMembership(user.getId(),group.getId());
}
}
}
/**
* 状态
*/
@Test
void testStatus(){
boolean status=taskService.createTaskQuery().taskId("taskId").singleResult().isSuspended();//Suspended=2中止,=1正常
if (status){//是否挂起(中止)
}
}
/**
* 终止删除流程、挂起(中止)
*/
@Test
void deletetask(){
Map<String,Object> map=new HashMap<>();
String processInstanceId="";
List<Task> procInsId = taskService.createNativeTaskQuery().
sql("select * from ACT_HI_TASKINST where PROC_INST_ID_ = #{procInsId} ORDER BY START_TIME_ desc")
.parameter("procInsId", processInstanceId).list();
if (!procInsId.get(1).getAssignee().equals("revokeVo.getUserCode()")){
map.put("result",false);
map.put("message","当前流程已不在下级节点!");
}
//作废-删除流程,删除流程历史记录
runtimeService.deleteProcessInstance(processInstanceId,"不请了删除流程实例");//删除任务,无processInstanceId任务时会报错。
historyService.deleteHistoricProcessInstance(processInstanceId);
runtimeService.suspendProcessInstanceById(processInstanceId);//挂起
runtimeService.activateProcessInstanceById(processInstanceId);;//激活
}
/**
* 获取下一节点信息(bpmn20.xml中的节点信息)
* @param taskId
*/
void getNextWork(String taskId){
Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult(); // 获取当前任务
List<SequenceFlow> outgoingFlows =((FlowNode)(repositoryService.getBpmnModel(currentTask.getProcessDefinitionId())
.getMainProcess()).getFlowElement(currentTask.getTaskDefinitionKey())).
getOutgoingFlows();
for (SequenceFlow outgoingFlow : outgoingFlows) {
String nextTaskId = outgoingFlow.getTargetRef();//节点的id,非任务
String nextTaskName = repositoryService.getBpmnModel(currentTask.getProcessDefinitionId()).getMainProcess().getFlowElement(nextTaskId).getName();
System.out.println("Next task ID: " + nextTaskId + ", Next task name: " + nextTaskName);
// taskService.setAssignee(nextTaskId,"三三");
// 下一个审批节点
FlowElement targetFlow = outgoingFlow.getTargetFlowElement();
//如果是用户任务则获取待办人或组
if (targetFlow instanceof UserTask)
{
// 如果下个审批节点为userTask
UserTask userTask = (UserTask) targetFlow;
userTask.setAssignee("主管审批");
}
}
}
// 删除流程 :(data:删除原因)
@Test
public void deleteProcessInstanceId(){
try{
String processInstanceId="a4b311ec-9e13-11ee-ac38-00ffb3ed8746";
//需要添加这段代码,否则审批意见表ACT_HI_COMMENT审批的userid是空的
// Authentication.setAuthenticatedUserId("老总");
//审核意见
//taskService.addComment("2122f113-9d70-11ee-bd37-00ffb3ed8746","5145648b-9b13-11ee-8409-00ffb3ed8746","批准");
runtimeService.deleteProcessInstance(processInstanceId,"删除原因");//删除运行中流程
historyService.deleteHistoricProcessInstance(processInstanceId);//删除历史流程
}catch (Exception e){
e.printStackTrace();
}
}
//提交申请
@Test
public void sub() {
// 员工提交请假申请
Map<String, Object> map = new HashMap<>();
map.put("days", 4);
map.put("name", "QQ");
map.put("reason","调价");
//map.put("userIds", "laozhao,laowang,lisi");//1、赋值审批分配人key
try{
//启动流程
ProcessInstance a1=runtimeService.startProcessInstanceByKey("leaveprocessmore",map.get("name").toString(),map);
Task task = taskService.createTaskQuery().processInstanceId(a1.getId()).singleResult();
/* Map<String,Object> m=new HashMap<>();
m.put("userIds", "老王,老赵");//2、赋值审批分配人key同1
taskService.setVariables(task.getId(),m);*/
//处理任务
taskService.complete(task.getId());
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 候选人为多个时需要先认领任务在处理,认领后其他人不能再操作此任务
*/
@Test
void taskClaim(){
//认领
List<Task> list = taskService.createTaskQuery().taskCandidateUser("QQ").list();
for (Task task : list) {
taskService.claim(task.getId(), "QQ");
}
//处理审批任务
list = taskService.createTaskQuery().taskAssignee("QQ").list();
for (Task task : list) {
Map<String, Object> leaves = new HashMap<>();
leaves.put("approved", true);
Authentication.setAuthenticatedUserId("QQ");
//审核意见
taskService.addComment("54981962-9e15-11ee-8123-00ffb3ed8746","54122175-9e15-11ee-8123-00ffb3ed8746","主管批准");
taskService.complete(task.getId(), leaves);
}
}
/**
* 认领任务
*/
@Test
void taskClaimUser() {
//认领
List<Task> list = taskService.createTaskQuery().taskCandidateUser("老赵").list();
for (Task task : list) {
taskService.claim(task.getId(), "老赵");
}
}
/**
* 放弃、取消已认领任务
*/
@Test
public void taskUnClaim() {
List<Task> list = taskService.createTaskQuery().taskAssignee("老赵").list();
//根据任务id查询
//Task task= taskService.createTaskQuery().taskId("").singleResult();
for (Task task : list) {
taskService.unclaim(task.getId());//放弃认领
}
}
/**
* 更换认领人
*/
@Test
public void taskClaimUserChange() {
//根据任务id查询
Task task= taskService.createTaskQuery().taskId("").singleResult();
taskService.setAssignee(task.getId(), "老王");
}
/**
* 附件应用-保存
* attachmenttype附件类型
* attachmentdescription附件说明
*/
@Test
void saveAttavhment(){
Attachment attachment=taskService.createAttachment("bpmn20.xml","0b0ffc62-9e16-11ee-8e0c-00ffb3ed8746",
"54122175-9e15-11ee-8123-00ffb3ed8746","流程文件","记录流程文件",
"E:\\springcloud\\flowable-master\\target\\classes\\processes\\请假more.bpmn20.xml");
taskService.saveAttachment(attachment);
}
/**
* 附件应用-查询
*/
@Test
void taskAttavhment(){
Attachment attachment=taskService.getAttachment("a3a30f3a-9e18-11ee-aef3-00ffb3ed8746");//获取附件对象
InputStream inputStream= taskService.getAttachmentContent("a3a30f3a-9e18-11ee-aef3-00ffb3ed8746");
List<Attachment> attachmentList=taskService.getProcessInstanceAttachments("54122175-9e15-11ee-8123-00ffb3ed8746");
for(Attachment att:attachmentList){
System.out.println("url:"+att.getUrl()+";description:"+att.getDescription());
}
}
/**
* 老板审批(批量)
*/
@Test
void approveMore(){
//查询所属任务
List<Task> list = taskService.createTaskQuery().taskCandidateGroup("boss").list();
Map<String,Object> leaves=new HashMap<>();
leaves.put("bossapproved", true);
for (Task task : list) {
taskService.complete(task.getId(), leaves);
if (true) {
//如果是同意,还需要继续走一步
Task t = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
if (t == null) {
System.out.println("工作流已完结!!!!");
break;
}
taskService.complete(t.getId());
}
}
}
/**
* 老板审批(单任务)、记录审批意见
*/
@Test
void approveSingle(){
//查询所属任务
Task task = taskService.createTaskQuery().processInstanceId("5145648b-9b13-11ee-8409-00ffb3ed8746").singleResult();
Map<String,Object> leaves=new HashMap<>();
leaves.put("bossapproved", true);
//需要添加这段代码,否则审批意见表ACT_HI_COMMENT审批的userid是空的
Authentication.setAuthenticatedUserId("老总");
//审核意见
taskService.addComment("2122f113-9d70-11ee-bd37-00ffb3ed8746","5145648b-9b13-11ee-8409-00ffb3ed8746","批准");
taskService.complete(task.getId(), leaves);
if (true) {
//如果是同意,还需要继续走一步
Task t = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
if (t == null) {
System.out.println("工作流已完结!!!!");
return;
}
taskService.complete(t.getId());
}
}
/**
*查询审批意见
*/
@Test
void queryTaskComment(){
List<Comment> taskComments = taskService.getTaskComments("2122f113-9d70-11ee-bd37-00ffb3ed8746");
for (Comment taskComment : taskComments) {
System.out.println("审批人:" + taskComment.getUserId());
System.out.println("审批意见:" + taskComment.getFullMessage());
}
}
/**
* 设置候选人
*/
@Test
void setCandidate(){
Map<String, Object> userIds = new HashMap<>();
userIds.put("userIds", "老赵,老王,老李");
ProcessInstance pi = runtimeService.startProcessInstanceByKey("leaveprocessmore",userIds);
System.out.println("id:"+pi.getId()+";activityId:"+pi.getActivityId());
}
/**
* 追加候选人
*/
@Test
void addCandidate(){
List<Task> list = taskService.createTaskQuery().taskCandidateUser("老赵").list();
List<Task> listt = taskService.createTaskQuery().taskId("taskId").list();
for (Task task : list)
taskService.addCandidateUser(task.getId(),"老李");
}
/**
* 删除候选人
*/
@Test
void delCandidate() {
List<Task> list = taskService.createTaskQuery().taskCandidateUser("老赵").list();
for (Task task : list) {
taskService.deleteCandidateUser(task.getId(), "老李");
}
}
/**
* 查询流程历史参与人记录
*/
@Test
void queryHistoryUser() {
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().list();
for (HistoricProcessInstance historicProcessInstance : list) {
List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForProcessInstance(historicProcessInstance.getId());
for (HistoricIdentityLink link : links)
System.out.println("userId:,taskId:,type:,processInstanceId:"+ link.getUserId()+";"+
link.getTaskId()+";"+ link.getType()+";"+ link.getProcessInstanceId());
}
}
/**
* 查询一个 Task的历史候选人与处理人
*/
void queryHistoryTaskUser() {
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().list();
for (HistoricTaskInstance historicTaskInstance : list) {
List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForTask(historicTaskInstance.getId());
for (HistoricIdentityLink link : links) {
System.out.println("userId:,taskId:,type:,processInstanceId:" + link.getUserId() + ";" +
link.getTaskId() + ";" + link.getType() + ";" + link.getProcessInstanceId());
}
}
}
/**
* 查询分配人或候选用户人的任务
*/
@Test
void querytask(){
//候选用户及多个候选用户时
List<Task> task = taskService.createTaskQuery().taskCandidateUser("李思思").list();
//分配人
List<Task> tasks = taskService.createTaskQuery().taskAssignee("李思思").list();
//候选用户或分配人
List<Task> task2 =taskService.createTaskQuery().taskCandidateOrAssigned("lisi").list();
System.out.println(task.size()+"=="+tasks.size());
}
/**
* 获取历史流程
*/
@Test
void queryHistory() {
List<ProcessInstance> processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey("leave").orderByStartTime().desc().list();
if (CollectionUtils.isEmpty(processInstance)) {
System.out.println("------------------------------------------");
}
// 获取最近的一个流程
List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstance.get(0).getId())
// 只查询已经完成的活动
.finished()
// 按照结束时间排序
.orderByHistoricActivityInstanceEndTime().desc()
.list();
List<String> collect = activities.stream().map(a -> "活动名称:" + a.getActivityName() + ";活动执行时间:" + a.getDurationInMillis() + "毫秒").collect(Collectors.toList());
for (String s : collect) {
System.out.println(s);
}
}
/**
* 请假列表(已完结、未完结)
*/
@Test
public void searchResult() {
String name = "李思思";
List<leaveInfo> leaveInfos = new ArrayList<>();
//已完结的
// List<HistoricProcessInstance> historicProcessInstances = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(name).finished().orderByProcessInstanceEndTime().desc().list();
//未完结的
List<HistoricProcessInstance> historicProcessInstances = historyService.createHistoricProcessInstanceQuery().
processInstanceBusinessKey(name).unfinished().orderByProcessInstanceStartTime().list();
for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
leaveInfo leaveInfo = new leaveInfo();
Date startTime = historicProcessInstance.getStartTime();
Date endTime = historicProcessInstance.getEndTime();
List<HistoricVariableInstance> historicVariableInstances = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(historicProcessInstance.getId())
.list();
for (HistoricVariableInstance historicVariableInstance : historicVariableInstances) {
String leaveName = historicVariableInstance.getVariableName();
Object value = historicVariableInstance.getValue();
if ("reason".equals(leaveName)) {
leaveInfo.setReason((String) value);
} else if ("days".equals(leaveName)) {
leaveInfo.setDays(Integer.parseInt(value.toString()));
} else if ("approved".equals(leaveName)) {
leaveInfo.setStatus((Boolean) value);
} else if ("name".equals(leaveName)) {
leaveInfo.setName((String) value);
}
}
leaveInfo.setStartTime(startTime);
leaveInfo.setEndTime(endTime);
leaveInfos.add(leaveInfo);
}
System.out.println(leaveInfos.size());
}
}
主管审批使用监听器动态设置候选人:
MyUserTaskListener代码:
package com.tt.flowable.lister;
import org.flowable.engine.RuntimeService;
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
/**
* 委派任务
* 监听器类型:任务监听器
* 主管审批动态分配候选人
*/
public class MyUserTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.addCandidateUser("老赵");
delegateTask.addCandidateUser("老王");
System.out.println("任务监听器:"+delegateTask.getId()+";"+delegateTask.getName());
}
}
主要表信息:
串行任务撤回(主动):
1、先判断是否可撤回:上一个节点是当前用户审核的,并且下一节点审批人还未处理时,可撤回到上一节点;
2、撤回步骤:保存并删除BpmnModel当前流程节点输出流;
3、把当前流程节点输出流方向改成上一个节点;
4、撤回任务完成后,再次恢复当前节点的原有输出流;
service
@Autowired
RepositoryService repositoryService;
//串行任务【主动撤回】
public void proBack(String instanceId,String canBackTaskId,String userId){
/*
1、先判断是否可撤回:上一个节点是当前用户审核的,并且下一节点审批人还未处理时,可撤回到上一节点;
2、撤回步骤:保存并删除BpmnModel当前流程节点输出流;
3、把当前流程节点输出流方向改成上一个节点;
4、撤回任务完成后,再次恢复当前节点的原有输出流;
*/
Map<String,Object> result = canBack(instanceId, canBackTaskId, userId);
Boolean canBack=(Boolean)result.get("canBack");
HistoricTaskInstance currentTask=(HistoricTaskInstance)result.get("currentTask");
if(!canBack){
System.out.println("流程:"+instanceId+",任务:"+canBackTaskId+"已经处理,不可撤回");
return;
}
//流程撤回
HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();//历史的【流程】实例
BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());//ProcessDefinitionId:leaveprocessmore:11:20f8063c-9641-11ee-b051-00ffb3ed8746
//上一流程
HistoricTaskInstance canBackTask = historyService.createHistoricTaskInstanceQuery().taskId(canBackTaskId).taskAssignee(userId).singleResult();
if(canBackTask==null){
System.out.println("未找到要回退的任务");
return;
}
FlowNode canBackNode = (FlowNode)bpmnModel.getFlowElement(canBackTask.getTaskDefinitionKey());
FlowNode currentNode=(FlowNode)bpmnModel.getFlowElement(currentTask.getTaskDefinitionKey());
//记录原始输出流程
List<SequenceFlow> sourceOuts =new ArrayList<>();
sourceOuts.addAll(currentNode.getOutgoingFlows());
//清理活动方向
currentNode.getOutgoingFlows().clear();
//创建新流
List<SequenceFlow> newFlows=new ArrayList<>();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentNode);//bossApproval老总
newSequenceFlow.setTargetFlowElement(canBackNode);//上一环节targetRef=supApproval(主管)
newFlows.add(newSequenceFlow);
currentNode.setOutgoingFlows(newFlows);
//记录备注信息
Authentication.setAuthenticatedUserId(userId);
taskService.addComment(currentTask.getId(), currentTask.getProcessInstanceId(), "撤回");
//完成撤回任务
taskService.complete(currentTask.getId());
//恢复原方向
currentNode.setOutgoingFlows(sourceOuts);
//删除当前任务历史记录,根据业务需要是否删除
historyService.deleteHistoricTaskInstance(currentTask.getId());
}
/**
* 判断流程是否能够撤回
* 上一个节点是当前用户审核,并且下一节点审批人还未处理时,可撤回到上一节点
* @return
*/
public Map<String,Object> canBack(String instanceId,String canBackTaskId,String userId){
boolean canBack=false;
Map<String,Object> map=new HashMap<>();
//查看流程历史节点
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(instanceId)
.orderByHistoricTaskInstanceStartTime()
.asc()
.list();
boolean hasUserTask=false;
HistoricTaskInstance currentTask=null;
for(HistoricTaskInstance task:list) {
if(task.getId().equalsIgnoreCase(canBackTaskId)
&&task.getAssignee().equalsIgnoreCase(userId)){
//找到了处理人处理的任务,查看下一个任务是否已经处理
hasUserTask=true;
continue;
}
if(hasUserTask){
//上一个任务是当前人处理的,查看当前任务是否已经被处理
hasUserTask=false;
if(null==task.getEndTime()){
canBack=true;
currentTask=task;
break;
}
}
}
if(canBack){
System.out.println("未处理的流程可撤回,任务ID:"+currentTask.getId()+",任务接收人:"+currentTask.getAssignee());
}
map.put("canBack",canBack);
map.put("currentTask",currentTask);
//方法返回值为:是否可回退、当前任务
return map;
}
controller
/**
* 撤回
* @param instanceId 流程id ACT_HI_PROCINST.PROC_INST_ID_
* @param canBackTaskId 想撤回到节点的id act_hi_taskinst.ID_
* @param userId 撤回人
*/
@GetMapping("/proBack")
public void proBack(String instanceId,String canBackTaskId,String userId) {
leaveService.proBack(instanceId,canBackTaskId,userId);
}