flowable工作流学习笔记

news2024/7/4 6:07:30

不同版本使用方式不一样,案例使用两个版本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);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1322072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ranger】CDP环境 更新 ranger 权限策略会发生低概率丢失权限策略的解决方法

一、问题描述&#xff1a; 我们的 kafka 服务在更新&#xff08;添加&#xff09; ranger 权限时&#xff0c;会有极低的概率导致 MM2 同步服务报错&#xff0c;报错内容 Not Authorized。但是查看 ranger 权限是赋予的&#xff0c;并且很早配置的权限策略也会报错。 相关组件…

有损编码——Wyner-Ziv理论

有损编码是一种在信息传输和存储中常见的编码技术&#xff0c;其主要目标是通过牺牲一定的信息质量&#xff0c;以换取更高的压缩效率。相比于无损编码&#xff0c;有损编码可以在保证一定程度的信息还原的前提下&#xff0c;使用更少的比特数来表示信息。Wyner-Ziv理论是一种重…

类的6个默认成员函数(上)

<center>Efforts of today and tomorrow.<center>今天的努力&#xff0c;明天的实力。开启本文&#xff01; 引入&#xff1a;  如果一个类中森么都没有&#xff0c;那么这个类就是空类&#xff0c;然而空类中真的是什么都没有吗&#xff1f; 其实不然&#xff0c…

第五节 操作符

第五节 操作符 目录 一&#xff0e; 操作符分类二&#xff0e; 算术操作符三&#xff0e; 移位操作符1. 二进制位2. 左移操作符3. 右移操作符 四&#xff0e; 位操作符1. 按位与2. 按位或3. 按位异或 五&#xff0e; 赋值操作符六&#xff0e; 单目操作符1. 概述2. sizeof是操作…

【Spring教程31】SSM框架整合实战:从零开始学习SSM整合配置,如何编写Mybatis SpringMVC JDBC Spring配置类

目录 1 流程分析2 整合配置2.1 步骤1&#xff1a;创建Maven的web项目2.2 步骤2:添加依赖2.3 步骤3:创建项目包结构2.4 步骤4:创建SpringConfig配置类2.5 步骤5:创建JdbcConfig配置类2.6 步骤6:创建MybatisConfig配置类2.7 步骤7:创建jdbc.properties2.8 步骤8:创建SpringMVC配置…

k8s安装配置dashboard

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

kali-捆绑应用程序

文章目录 一、安装开发环境二、开始捆绑三、开始监听 操作环境 kali windows 一、安装开发环境 ┌──(kali㉿kali)-[~] └─$ sudo -i [sudo] kali 的密码&#xff1a;┌──(root㉿kali)-[~] └─# whoami root┌──(root㉿kali)-[~] └─# apt update …

(七)STM32 NVIC 中断、优先级管理及 AFIO 时钟的开启

目录 1. 中断相关知识简介 1.1 什么是中断 1.2 什么是内中断、外中断 1.3 什么是可屏蔽中断、不可屏蔽中断 2. CM3 内核中断介绍 2.1 F103系统异常清单 2.2 F103 外部中断清单 3. NVIC 简介 3.1 NVIC 寄存器简介 3.2 NVIC 相关寄存器的介绍 4. 中断优先级 4.1 优先…

uni-app 用于开发H5项目展示饼图,使用ucharts 饼图示例

先下载ucharts H5示例源码&#xff1a; uCharts: 高性能跨平台图表库&#xff0c;支持H5、APP、小程序&#xff08;微信小程序、支付宝小程序、钉钉小程序、百度小程序、头条小程序、QQ小程序、快手小程序、360小程序&#xff09;、Vue、Taro等更多支持canvas的框架平台&#…

洛谷 P8794 [蓝桥杯 2022 国 A] 环境治理

文章目录 [蓝桥杯 2022 国 A] 环境治理题目链接题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 思路解析CODE给点思考 [蓝桥杯 2022 国 A] 环境治理 题目链接 https://www.luogu.com.cn/problem/P8794 题目描述 LQ 国拥有 n n n 个城市&#xff0c;从 0 0 …

【无标题】CTF之SQLMAP

拿这一题来说 抓个包 复制报文 启动我们的sqlmap kali里边 sqlmap -r 文件路径 --dump --dbs 数据库 --tables 表

【老牌期刊】IF:6+,2天预审,3-5个月录用!

期刊简介 1区计算机智能类SCI 【期刊概况】IF&#xff1a;6.0-7.0&#xff0c;JCR1区&#xff0c;中科院2区&#xff1b; 【终审周期】走期刊部系统&#xff0c;3个月左右录用&#xff1b; 【检索情况】SCI检索&#xff1b; 【WOS收录年份】2018年&#xff1b; 【自引率】…

远程桌面连接软件除了Todesk还能用什么?

自从昨天Todesk开始报错&#xff0c;开始寻找好用的远程桌面连接软件&#xff0c;目前尝试了解到的免费的有RayLink和Teamviewer&#xff1a; TeamViewer&#xff1a;就个人来讲&#xff0c;感觉用户体验不是很友好 &#xff1a; 有什么好用的免费的远程桌面连接软件&#xff0…

html中RGB和RGBA颜色表示法

文章目录 RGB什么是RGBRGB颜色模式的取值范围RGB常用颜色对照表 RGBA什么是RGBARGBA颜色模式的取值范围 总结 RGB 什么是RGB RGB是一种颜色空间&#xff0c;其中R代表红色&#xff08;Red&#xff09;、G代表绿色&#xff08;Green&#xff09;、B代表蓝色&#xff08;Blue&a…

外汇天眼:芝加哥商品交易所(CME)对鹰七公司规定进行罚款

国际衍生品市场CME集团发布了一则关于Eagle Seven LLC的纪律行动通知。根据一项和解提议&#xff0c;其中Eagle Seven LLC既未承认也未否认任何违规规定或事实陈述&#xff0c;纽约商业交易所&#xff08;NYMEX&#xff09;业务行为委员会的一个小组认定&#xff0c;在2022年2月…

非专业的建模人员如何给模型设置材质纹理贴图?

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 1、材质和纹理的区别于关联 材质&#xff08;Material&#xff09;是…

【HarmonyOS开发】ArkTs实现侧栏联动

侧栏联动功能模块&#xff1a; 1、切换左侧导航&#xff0c;右侧滚动到对应的内容。 2、滚动右侧的内容&#xff0c;左侧会切换对应的导航。 1、涉及知识点 List&#xff08;列表组件&#xff09;、ListItemGroup&#xff08;列表item分组&#xff09; 2、效果预览 3…

AI数字人短视频矩阵怎么做?

AI数字人是什么&#xff1a;你可以通过我们的系统克隆出多个AI形象&#xff0c;每个数字人都有独特的个性和风格。无论是批量制作短视频&#xff0c;还是直播7*24小时都可以。数字人就是用AI技术根据你的真人形象克隆出一个数字人&#xff0c;跟真人的相似度可以达到100&#x…

【FAQ】运动健康服务云侧数据常见问题及解答

目录 Q1&#xff1a;v2接口相比于v1接口传参及返回的数据格式有变化吗&#xff1f;是否可以直接将v1接口改成v2接口&#xff1f; Q2&#xff1a;如何获取采集健康数据的穿戴设备信息&#xff1f; Q3&#xff1a;如何处理非华为手机产生的步数调用采样明细接口查询不到数据&a…

STM32-I2C通讯-AHT20温湿度检测

非常感谢&#xff0c;提供的视频学习 https://www.bilibili.com/video/BV1QN411D7ak/?spm_id_from333.788&vd_source8ca4826038edd44bb618801808a5e076 该文章注意&#xff1a;串口显示中文会乱码&#xff0c;必须选用支持ASCII的串口助手&#xff0c;才能正常显示中文。…