Activiti7进阶(流程定义+流程实例+任务负责人+任务候选人)(3.8)

一、流程定义相关

1.流程定义查询:

首先需要再生成一套报销流程leaveProcess2,再部署上去。

一是查询指定流程定义,二是查询所有流程定义。

 @Test
    // 查询流程定义
    public void testQueryProcessDefinition(){
        // 获取流程引擎对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //获取 仓库 RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();

        /* 1.按照指定条件查询
         * 通过 Activiti API 来实现查询 creatXxxQuery(); 需要什么条件则添加条件即可。
         *         ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
         *                 .processDefinitionKey("leaveProcess") //只要请假流程
         *                 .latestVersion() //最后一个版本
         *                 .singleResult();
         *
         *         System.out.println("流程定义Id: " + processDefinition.getId());
         *         System.out.println("流程定义名称: " + processDefinition.getName());
         *         System.out.println("流程定义Key: " + processDefinition.getKey());
         *         System.out.println("流程定义对应的部署Id: " + processDefinition.getDeploymentId());
         */
        //2.查询所有流程定义
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery()
                .list();
        for (ProcessDefinition processDefinition : processDefinitionList){

            System.out.println("流程定义Id: " + processDefinition.getId());
            System.out.println("流程定义名称: " + processDefinition.getName());
            System.out.println("流程定义Key: " + processDefinition.getKey());
            System.out.println("流程定义对应的部署Id: " + processDefinition.getDeploymentId());
            System.out.println("-------------------");
        }
    }

2.流程定义删除:

删除一条流程定义需要同时删除流程定义表act_re_procdef,以及它的部署表act_re_deployment二进制数据表act_ge_bytearray

1.删除时我们使用的是 RepositoryService.deleteDeployment(DeploymentId)。我们删除的是部署,同时删除对应的流程定义。

2.删除流程定义注意事项:

  • 若当前流程定义下没有正在执行的流程实例,我们可以直接将其删除。

    // 删除流程定义
        public void testDeleteProcessDefinition(){
            //获取流程引擎对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    
            //获取 仓库 RepositoryService
            RepositoryService repositoryService = processEngine.getRepositoryService();
    
            //模拟流程定义对应的部署Id (上面查询得到的)
            String deploymentId = "10001";
            
            //调用 Activiti API 来删除指定流程定义
            repositoryService.deleteDeployment(deploymentId);
    
        }
    
  • 若当前流程定义下有正在执行的流程实例。则抛异常(外键异常)

    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`activiti_03_demo`.`act_ru_execution`, CONSTRAINT `ACT_FK_EXE_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`))
    

    解决方案添加第二个参数 将 cascade 设置为true,本质上就是将 MySQL 数据库中外键删除策略改为 cascade。

    // 删除流程定义
        public void testDeleteProcessDefinition(){
            //获取流程引擎对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    
            //获取 仓库 RepositoryService
            RepositoryService repositoryService = processEngine.getRepositoryService();
    
            //模拟流程定义对应的部署Id (上面查询得到的)
            String deploymentId = "10001";
            //调用 Activiti API 来删除指定流程定义
            //repositoryService.deleteDeployment(deploymentId);
            repositoryService.deleteDeployment(deploymentId,true);
    
        }
    

3.流程资源下载:

流程定义表act_re_procdef中,我们可以拿到流程定义的文件名和图片,

image-20260313202129726

二进制数据表act_ge_bytearray中所对应。

image-20260313202249088

1. 我们部署时需要将流程文件(bpmn) 和流程图 (png) 一并部署到 Activiti7 中。
  - 需要注意:部署时需要 bpmn 文件名和 png 图片名字一致。
2. 我们可以查询指定流程定义对象。
  - 获取 bpmn 和 png 图片名。
  - 获取部署 ID
3. 我们就可以通过 RepositoryService 中的 API 。通过文件名及部署 ID 就可以拿到 png 和 bpmn 资源文件的 InputStream 2个输入流。
4. 我们需要定义两个输出流(要将文件下载到哪里)。
5. 通过 commons-io  文件上传下载工具中的 copy 方法。将输入流内的内容存储到输出流中。
6. 通过输出流将数据保存到本地。
7. 关闭资源。
    // 流程资源下载
    @Test
    public void testDownloadFile() throws Exception {
        //获取流程引擎对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取 仓库 RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //查询流程定义对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("leaveProcess")
                .latestVersion()
                .singleResult();
        //获取部署ID
        String deployId = processDefinition.getDeploymentId();
        //获取 bpmn 文件名
        String bpmnFileName = processDefinition.getResourceName();
        //获取 png 文件名
        String pngFIleName = processDefinition.getDiagramResourceName();
        // 通过 Acitiviti --> RepositoryService 中的API 通过文件名+部署ID 就可以拿到指定的资源文件
        // 获取bpmn 资源文件
        InputStream bpmnInputStream = repositoryService.getResourceAsStream(deployId, bpmnFileName);
        //获取 png资源文件
        InputStream pngInputStream = repositoryService.getResourceAsStream(deployId, pngFIleName);
        //自定义输出流
        //bpmn 输出流
        FileOutputStream bpmnOutputSteam = new FileOutputStream("D:/LeaveProcess.bpmn");
        //png 输出流
        FileOutputStream pngOutputSteam = new FileOutputStream("D:/LeaveProcess.png");
        // 通过 commons-io 将输入流的内容存储到输出流中
        IOUtils.copy(bpmnInputStream, bpmnOutputSteam);
        IOUtils.copy(pngInputStream, pngOutputSteam);
        //关闭资源 先用后关,后用先关
        pngOutputSteam.close();
        bpmnOutputSteam.close();
        pngInputStream.close();
        bpmnInputStream.close();
    }

4.流程定义版本问题:

1. 我们存在两个 bpmn 建模文件。A文件及B文件。他们的 id 相同。
2. 此时若
  - A  ==> A.bpmn 文件先部署
  - B  ==> B.bpmn 文件再部署
3. 此时会怎么样?
  - 报错。                                     不会报错
  - B 覆盖 A。                              不会覆盖
  - A 不会被覆盖。                      B也存在
  - 两个文件共存。                      答案是两个文件都共存
4. 既然共存就必须存在区分。通过 version 字段来区分。后部署的版本号更大。
5. 若现在启动流程。那么按照哪个版本的执行?
  - 会按照版本号大的来启动流程。(后部署的)。
6. 若 A 先部署,并存在正在执行的流程实例。此时再部署 B 文件。
  - 正在执行的流程实例按照哪套执行?
  - 新发起的流程实例按照哪套执行?
7. 结论。
  - 有正在执行的流程实例,依然按照老的流程实例执行。
  - 新发起的流程实例,按照版本号大的执行。

假设对 leaveProcess 流程做了 3 次部署:

部署顺序 部署 ID 生成的流程定义 ID 流程版本号 对应关系
第一次部署 1 leaveProcess:1:4 1 版本 1 的流程定义 ↔ 部署 ID=1
第二次部署 2 leaveProcess:2:10 2 版本 2 的流程定义 ↔ 部署 ID=2
第三次部署 3 leaveProcess:3:15 3 版本 3 的流程定义 ↔ 部署 ID=3

二、流程实例相关

1.什么是流程实例?

用户或程序按照流程定义内容发起一个流程,这就是一个流程实例。

2.BusinessKey(业务标识)

(1)如何使用?

​ ①首先请假人填写请假单,将这条请假单信息插入到业务表中。可以获取到这条业务id。这个业务id就是我们要的 BusinessKey

​ ②我们在启动流程之前,就将 BusinessKey 绑定进去。这样 BusinessKey 就会绑定到生成的这条流程实例中。

​ ③当我们发起流程后,走到每个节点,节点将生成任务。

​ ④对应任务的审批人就可以通过当前任务 id ---> 查询到流程实例id ---> 在流程实例中获取到绑定的 BusinessKey

​ ⑥在通过 BusinessKey 到业务表中查询业务数据,展示到页面上。

​ ⑦审批人就可以通过业务数据来决定是否同意/拒绝审批。

image-20260313210945822

(2)启动并绑定BusinessKey

只需在startProcessInstanceByKey()方法的参数列表中,再添加一个参数businessKey

 // 启动流程并绑定 BusinessKey
    public void testStartProcessBindBusiness(){
        // 获取流程引擎对象
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取运行时 RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 模拟 BusinessKey
        String businessKey = "8001";
        ProcessInstance processInstance =
                runtimeService.startProcessInstanceByKey("leaveProcess",businessKey);

    }

(3)获取 BusinessKey

与正常查询待办任务几乎一致,另外需要 runtimeService。

获取到taskList后 ---> 遍历其中的任务 ---> 根据任务可以获取到 流程实例id ---> 根据流程实例id 查询流程实例对象 ---> 根据流程实例对象 获取到BusinessKey

注意:

  • 业务表中的id就是BusinessKey,在启动流程实例之前就把BusinessKey绑定到流程实例中了。

  • 而在流程实例中存在流程实例idBusinessKey:

    • 流程实例id = Activiti 内部的唯一标识(自动生成)
    • BusinessKey = 业务表的唯一标识 (手动传入)
 //查询任务时就可以获取到 BusinessKey
    @Test
    public void testQueryTaskAndBusiness(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        //另外需要  runtimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //我 = 审批人 = 当前登录用户
        String userId = "张三";
        List<Task> taskList= taskService.createTaskQuery()
                .processDefinitionKey("leaveProcess") // 根据Key确定某个流程(请假流程)
                .taskAssignee(userId)
                .list();
        for (Task task : taskList) {
            System.out.println("任务id: " + task.getId());
            System.out.println("任务名称: " + task.getName());
            System.out.println("任务负责人: " + task.getAssignee());
            System.out.println("任务创建时间: " + task.getCreateTime());

            System.out.println("任务对应的流程实例id: " + task.getProcessInstanceId());
            System.out.println("任务对应的流程定义id: " + task.getProcessDefinitionId());
            
            //根据流程实例id 查询流程实例对象
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            System.out.println("Business: " + processInstance.getBusinessKey());
            System.out.println("----------------");

        }

    }

3.流程定义/实例挂起/激活:

  1. 例如公司制度改变过程中需要更换总经理, 总经理更换过程中,有100个人的流程, 70个人已经完成了,30个人未完成.此时若更换总经理必须老的总经理将流程都审批结束后,新的总经理才能上任。(单独挂起流程定义

    • 等待的过程中:此时我们需要挂起流程定义,不让发起新的流程实例。

      // 若当前流程定义是激活,那么我们可以挂起流程定义
      repositoryService.suspendProcessDefinitionById(processDefinition.getId());
      System.out.println("当前流程定义已被挂起");
      
    • 换任结束后:当更换完成后,我们再激活流程定义。此时就可以再次发起流程实例。

      // 若当前流程定义是挂起,那么我们可以激活流程定义
      repositoryService.activateProcessDefinitionById(processDefinition.getId());
      System.out.println("当前流程定义已被激活");
      
  2. 比如我们的业务流程为:

    【开始节点】-->【A节点】-->【B节点】-->【C节点】-->【结束节点】

    【C节点】的业务逻辑需要和外部接口交互,刚好外部接口出问题了,如果剩下的流程都走到【C节点】,执行【C节点】的业务逻辑,那都会报错。(挂起流程定义及对应的流程实例

    • 到C节点就报错:此时我们不能再让发起新的流程实例,正在执行的任务也不能继续执行了。挂起流程定义及流程实例。

      // 若当前流程定义及对应的流程实例是激活,那么我们可以挂起流程定义
      // 3个参数  (需要挂起流程定义的id,boolean 是否挂起对应的流程实例,指定时间挂起 null 立即挂起)
      repositoryService.suspendProcessDefinitionById(
               processDefinition.getId(),true,null);
      System.out.println("当前流程定义及对应的流程实例已被挂起");
      
    • 等待外部接口修复后:激活流程定义及流程实例。

      //若当前流程定义即对应的流程实例是挂起,那么我们可以激活流程定义
      repositoryService.activateProcessDefinitionById(
                processDefinition.getId(),true,null);
      System.out.println("当前流程定义及对应的流程实例已激活");
      

    3.评分流程:可设置多级评分,评分流程会按照从上往下的顺序,依次评分;评分人必须在评分截至时间内完成评分,否则不允许继续评分,流程将会挂起,停止流转;(单独挂起流程实例

    • 若时间到:我们需要挂起流程实例。

      // 当前流程实例是激活状态,我们需要挂起流程实例
      runtimeService.suspendProcessInstanceById(processInstanceId);
      System.out.println("当前流程实例已挂起");
      
    • 都结束后:激活流程实例。

      // 当前流程实例已被挂起,我们需要激活流程实例
      runtimeService.activateProcessInstanceById(processInstanceId);
      System.out.println("当前流程实例被激活");
      

三、任务分配负责人

1.固定分配:

在图中将负责人写死。

弊端:硬编码。也不能选择。强烈不推荐。

image-20260313221735857

2.UEL表达式分配:

强烈推荐。

①画图时,我们再 Assignee 负责人部分使用 ${变量} 占位。

image-20260313222311468

②为变量赋值:

此时,我们将第二个财务审批改为UEL表达式形式。

在部署时是没有问题的,启动流程实例后,当第一个总经理审批同意后,走到第二个财务审批,如果没有为UEL 表达式中的变量赋值则会报错。

Caused by: javax.el.PropertyNotFoundException: Cannot resolve identifier 'assignee1'

如何解决:一种是启动流程时,一起赋值;二是 到达当前任务时,才为变量赋值。

1. 启动流程时,立即为该 UEL 表达式中的变量赋值。
 - 我们需要定义一个 Map<String,Object>。
 - 使用 map.put("流程图中UEL表达式的Key名字","赋值");
 - 将该 Map 交给启动流程的参数中即可。
 - 注意:添加map的 Key 必须与 UEL 表达式中的变量名 相同!!!
2. 到达当前任务时,才为变量赋值。(项目中才使用)
//启动流程并绑定 负责人
@Test
public void testStartProcessBindAssignee(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
	//为UEL 表达式中的部门负责人赋值
    Map<String,Object> params = new HashMap<>();
    params.put("assignee1,"小红");
	ProcessInstance processInstance =runtimeService.startProcessInstanceByKey( 
        		"leaveProcess",params);

若为多个UEL表达式,只需再多put一个就好。

 Map<String,Object> variables  = new HashMap<String, Object>();
    variables.put("assignee0","zhangsan");
    variables.put("assignee1","lisi");
    //根据流程定义的key启动流程实例,这个key是在定义bpmn的时候设置的
    ProcessInstance instance = runtimeService
            .startProcessInstanceByKey("leaveProcess",variables);

3.监听器分配:

了解即可。

  • 任务监听器是发生对应的任务相关事件时执行自定义的Java逻辑或表达式。
  • 任务相关事件包括:
    • Event:
      • Create:任务创建后触发。
      • Assignment:任务分配后触发。
      • Delete:任务完成后触发。
      • All:所有事件发生都触发。
  1. 自定义一个任务监听器类,然后此类必须实现org.activiti.engine.delegate.TaskListener接口

    package cn.wolfcode;
    
    import org.activiti.engine.delegate.DelegateTask;
    import org.activiti.engine.delegate.TaskListener;
    
    /**
     * Created by wolfcode
     */
    public class AssigneeTaskListener implements TaskListener {
        public void notify(DelegateTask delegateTask) {
            if(delegateTask.getName().equals("部门经理审批")){
                delegateTask.setAssignee("赵六");
            }else if(delegateTask.getName().equals("人事复批")){
                delegateTask.setAssignee("孙七");
            }
        }
    }
    
  2. 在bpmn文件中配置监听器

image-20210603114503828

  • 在实际开发中,一般也不使用监听器分配方式,太麻烦了。

四、任务候选人

  1. 候选人不是负责人。他也可以使用 UEL 表达式的方式。

  2. 当我们的审批有多人都可以完成的情况下,我们就可以使用任务候选人。

  3. 当候选人查询到任务后,需要领取任务,升级为负责人。其他候选人全部失效。

  4. 如何使用?

    ①画图时,我们不能再 Assignee 中书写了。需要再 Candidate Users 中书写。多个候选人使用 , 分割。

image-20260313225452957

```java
// 启动流程并绑定 候选人
public void testStartProcessBindcandidate(){
// 获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取运行时 RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 为 UEL表达式 中的部门负责人赋值
Map<String,Object> params = new HashMap<>();
params.put("assignee0","小帅");
params.put("assignee1","小美");

ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("leaveProcess",params);

}
```

走到该处后,负责人为空,也就是需要候选人领取该任务才行。

②查询任务时,我们需要根据候选人查询。与查询几乎一致,只需更改taskCandidateUser,可以查一个人,也可以查列表。

java List<Task> taskList = taskService.createTaskQuery() .processDefinitionKey("leaveProcess") // 根据Key确定某个流程(请假流程) .taskCandidateUser(userId) // 候选人 .list();

③完成任务时,需要先领取任务,升级为负责人才能审批任务。

候选人领取任务后,再完成任务。

```java
//候选人领取任务
@Test
public void testClaimTask(){
//获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取 任务 TaskService
TaskService taskService = processEngine.getTaskService();

//候选人id
String userId = "小帅";
//任务id
String taskId = "15007";

//领取任务
taskService.claim(taskId,userId);
//完成任务
taskService.complete(taskId)
}
```

posted on 2026-03-13 23:26  冬冬咚  阅读(39)  评论(0)    收藏  举报