Administrator
Published on 2025-06-08 / 1 Visits
0
0

Activiti7工作流

Activiti7工作流

概述

工作流就是用来应对流程经常变动的情况,例如请假审批流程,如果使用硬编码方式实现审批流程就会非常被动,因为审批流程可能会经常变动,如果使用硬编码方式实现,如果流程一变动那编写的代码流程就要随着变动,所以直接使用硬编码实现流程是非常不明智的选择。使用工作流就是为了让我们写的代码能以不变应万变。

activiti 架构图

image-20250607152137260

在新版本中,我们通过实验可以发现IdentityService,FormService两个Serivce都已经删除了。 所以后面我们对于这两个Service也不讲解了,但老版本中还是有这两个Service。

activiti.cfg.xml文件:配置信息,例如连接数据库等,可以通过编程式实现

ProcessEngineConfiguration接口:用于读取 activiti.cfg.xml配置文件

Service接口:用于对流程进行管理,其中前4个是最长用的

  • RepositoryService——activiti的资源管理类
  • RuntimeService——activiti的流程运行管理类
  • TaskService——activiti的任务管理类
  • HistoryService——activiti的历史管理类
  • ManagerService——activiti的引擎管理类

基本使用

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter</artifactId>
    <version>7.1.0.M6</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- druid 连接池管理 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- mySQL数据库驱动包管理 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis plus 集成Spring Boot启动器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

配置activiti数据库连接源

@Configuration
public class ActivitiConfig {

    @Bean
    @Autowired
    public ProcessEngineConfiguration processEngineConfiguration(DataSource dataSource){
        StandaloneProcessEngineConfiguration spec = new StandaloneProcessEngineConfiguration();
        spec.setDataSource(dataSource);
        spec.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        return spec;
    }

}

测试,看ProcessEngine是否为空

@Component
public class MyTest {
    @Autowired
    ProcessEngine processEngine;

    public void run(){
        System.out.println(processEngine);
    }
}

运行成功后,数据库中就会创建25张表

Activiti 的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的API对
应。
ACT_RE_: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,
规则,等等)。
ACT_RU_
: 'RU'表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,
等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删
除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI_: 'HI'表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等
等。
ACT_GE_
: GE表示general。通用数据, 用于不同场景下。

安装Activti BPMN visualizer插件来可视化创建BPMN流程图。

安装后再resources中右键新增New Activiti 6.x BPMN 2.0 file,然后选择创建的xml文件右键选择View BPMN (Activiti) Diagram,就可以在插件中编辑BPMN流程图了

image-20250606223906933

流程图有直接的id,还有流程中的任务有任务名称和执行人等,这些都可以通过界面来编写完成。

image-20250607180509101

定义并部署一个流程

// 1.流程定义部署
// act_ge_bytearray(部署信息)、
// act_re_procdef(流程定义的一些信息)、
// act_re_deployment(流程定义的bmpn文件和png文件)
@Test
public void testProcessEngine(){
    //创建ProcessEngine持久服务,用于写入数据库对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("test.bpmn20.xml")
            .addClasspathResource("diagram.png")
            .name("请假审批流程")
            .deploy();
    System.out.println(deployment.getName());
    System.out.println(deployment.getId());

}

创建并启动一个流程实例

//2.启动一个流程实例(多个流程实例【对象实例】对应一个流程定义【类】)
// act_hi_actinst 活动历史表,记录所有活动
// act_hi_identitylink 参与者信息
// act_hi_procinst 流程实例历史表 
// act_hi_taskinst 任务历史表,记录所有任务
// act_ru_executin 流程实例执行表
// act_ru_identitylink 任务参与者
// act_ru_task 任务执行表
@Test
public void startProcessInstance(){
    RuntimeService runtimeService = processEngine.getRuntimeService();
    //通过流程定义的key(可以在流程定义的xml中查看id)获取流程实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidays");
    System.out.println("流程定义id:"+processInstance.getProcessDefinitionId());//holidays:1:9c37ad72-434e-11f0-82ce-3cf011eabf6f
    System.out.println("流程部署id:"+processInstance.getDeploymentId());//null
    System.out.println("流程实例id:"+processInstance.getId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
    System.out.println("当前活动id:"+processInstance.getActivityId());//null
}

查询当前流程的个人任务

//查询当前流程的个人任务
@Test
public void queryPersonalTaskList(){
    // 流程定义key
    String processDefinitionKey = "holidays";
    // 任务负责人
    String assignee = "bob";
    //获取任务服务对象
    TaskService taskService = processEngine.getTaskService();
    //创建一个任务查询
    List<Task> list = taskService.createTaskQuery()
            .processDefinitionKey(processDefinitionKey)
            .includeProcessVariables()
            .taskAssignee(assignee).list();

    for (Task task : list) {
        System.out.println("----------------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
        System.out.println("任务id:" + task.getId());//f98be3c5-434e-11f0-ad6a-3cf011eabf6f
        System.out.println("任务负责人:" + task.getAssignee());//bob
        System.out.println("任务名称:" + task.getName());//填写请假申请
    }
}

完成一个流程中的某个任务

//完成个人实例任务
//act_hi_actinst 已完成的活动信息和下一个待完成的任务
//act_hi_identitylink 参与者
//act_hi_taskinst 任务实例
//act_ru_executin 执行表
//act_ru_identitylink 参与者信息
// act_ru_task 任务
@Test
public void completTask(){
    // 流程定义key
    String processDefinitionKey = "holidays";
    // 任务负责人
    String assignee = "bob";
    //获取任务服务对象
    TaskService taskService = processEngine.getTaskService();
    //创建一个任务查询
    List<Task> list = taskService.createTaskQuery()
            .processDefinitionKey(processDefinitionKey)
            .includeProcessVariables()
            .taskAssignee(assignee).list();
    for (Task task : list) {
        System.out.println("----------------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
        System.out.println("任务id:" + task.getId());//f98be3c5-434e-11f0-ad6a-3cf011eabf6f
        System.out.println("任务负责人:" + task.getAssignee());//bob
        System.out.println("任务名称:" + task.getName());//填写请假申请
    }
    //todo做别的一些事,例如保存填写信息等
    //完成当前任务
    taskService.complete(list.get(1).getId());//f98be3c5-434e-11f0-ad6a-3cf011eabf6f
    System.out.println("完成任务id:"+list.get(1).getId());

}

删除成功部署的流程,会删除所有有关的流程部署表

//删除已经成功部署的流程定义
@Test
public void deleteDeployment(){
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //流程删除分为两种:普通删除、级联删除,通过第二个参数区别,fasle为普通删除,true为级联删除
    //如果该流程定义下没有正在运行的流程,则可以用普通删除
    /*如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关
    记录全部删除。项目开发中使用级联删除的情况比较多,删除操作一般只开放给超级管理员使
    用。*/
    repositoryService.deleteDeployment("9c109d6f-434e-11f0-82ce-3cf011eabf6f",true);
    repositoryService.deleteDeployment("4120dca9-4351-11f0-b9ba-3cf011eabf6f",true);
    repositoryService.deleteDeployment("e858dbf3-4369-11f0-a74e-3cf011eabf6f",true);
    System.out.println("级联删除部署的流程—————成功");
}

进阶使用

流程实例(Businesskey)

参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例,是动态的,流程实例就相当于类中的的实例一样。

流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流 程的一次执行。比如部署系统请假流程后,如果某用户要申请请假这时就需要执行这个流程,如果 另外一个用户也要申请请假则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。

启动流程实例并指定业务表示( Businesskey)

启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。

//2.启动一个流程实例(多个流程实例【对象实例】对应一个流程定义【类】)
// act_hi_actinst 活动历史表,记录所有活动
// act_hi_identitylink 参与者信息
// act_hi_procinst 流程实例历史表 
// act_hi_taskinst 任务历史表,记录所有任务
// act_ru_executin 流程实例执行表
// act_ru_identitylink 任务参与者
// act_ru_task 任务执行表
@Test
public void startProcessInstance(){
    RuntimeService runtimeService = processEngine.getRuntimeService();
    //通过流程定义的key(可以在流程定义的xml中查看id)和Businesskey(业务id)获取流程实例,其中业务id可以是请假单的具体内容表的id,方便后面去查看请假单具体内容
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidays","112111");
    System.out.println("流程定义id:"+processInstance.getProcessDefinitionId());//holidays:1:9c37ad72-434e-11f0-82ce-3cf011eabf6f
    System.out.println("流程部署id:"+processInstance.getDeploymentId());//null
    System.out.println("流程实例id:"+processInstance.getId());//f9868c91-434e-11f0-ad6a-3cf011eabf6f
    System.out.println("当前活动id:"+processInstance.getActivityId());//null
}

image-20250607161532993

流程实例运行时(getRuntimeService)

流程在运行过程中可以查询流程实例的状态,当前运行结点等信息。

//查询流程实例运行(执行)时的信息
    @Test
    public void queryProcessRuntimeInfoList(){
        // 流程定义过程key
        String processDefinitionKey = "holidays";
        RuntimeService runtimeService = processEngine.getRuntimeService();
        List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
                .processDefinitionKey(processDefinitionKey)
                .list();
        for (ProcessInstance processInstance : list) {
            System.out.println("----------------------------");
            System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
            System.out.println("所属流程定义id:"+processInstance.getProcessDefinitionId());
            System.out.println("是否执行完成:" + processInstance.isEnded());
            System.out.println("是否暂停:" + processInstance.isSuspended());
            System.out.println("当前活动标识:" + processInstance.getActivityId());
            System.out.println("Businesskey(外部系统业务id):"+processInstance.getBusinessKey());
        }
    }

流程挂起与激活

操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成 该流程实例的当前任务将报异常。

1.流程定义挂起与激活

操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停: 流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将 全部挂起暂停执行

//流程定义的挂起与激活
@Test
public void suspendOrActivateProcessDefinition(){
    String processDefinitionId="holidays:1:da0c89d0-4373-11f0-9533-3cf011eabf6f";
    String processDefinitionKey="";
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //获取流程定义对象
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
            .processDefinitionId(processDefinitionId)
            .singleResult();
    //查看是否时挂起状态
    boolean suspended = processDefinition.isSuspended();
    if (suspended){
        //流程定义id、全部激活(该流程定义下的所有实例)、激活日期
        repositoryService.activateProcessDefinitionById(processDefinitionId,true,null);
    }else {
        //流程定义id、全部挂起(该流程定义下的所有实例)、挂起日期
        repositoryService.suspendProcessDefinitionById(processDefinitionId,true,null);
    }
}

2.流程实例挂起与激活

操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成 该流程实例的当前任务将报异常。

//流程定义的挂起与激活
@Test
public void suspendOrActivateProcessInstance(){
    String processInstanceId="670586b7-4377-11f0-abfe-3cf011eabf6f";
    String processInstanceKey="";
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
            .processInstanceId(processInstanceId)
            .singleResult();

    //查看是否时挂起状态
    boolean suspended = processInstance.isSuspended();
    if (suspended){
        //流程实例id
        runtimeService.activateProcessInstanceById(processInstanceId);
        System.out.println("流程实例激活成功!流程实例id:"+processInstanceId);
    }else {
        //流程实例id
        runtimeService.suspendProcessInstanceById(processInstanceId);
        System.out.println("流程实例挂起成功!流程实例id:"+processInstanceId);
    }

}

分配任务负责人(流程变量)

1.固定分配

在业务建模时指定固定的任务负责人,在基本使用章节,我们就是使用的固定分配。在properties 视图中,填写Assignee项为任务负责人。由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照 bpmn 的配置去分配任 务负责人,所以该方式属于硬编码不够灵活(不推荐使用)

image-20250607180423071

2.表达式分配

1)UEL 表达式

Activiti 使用 UEL表达式,UEL是java EE6规范的一部分,UEL(Unified Expression Language)即统一表达式语言,activiti支持两个UEL表达式:UEL-value和UEL-method。

UEL-value:{userId}、{username}

UEL-method:${user.id},通过getter方法获取值。

UEL-method与UEL-value结合:${userService.getUserNameById(userId)}

其他:基础类型、bean、list、array和map

2)使用流程变量设置值

image-20250607205742995

image-20250607205834761

在启动流程实例时设置流程变量

//使用流程变量设置值
@Test
public void startProcessInstance(){
    RuntimeService runtimeService = processEngine.getRuntimeService();
    HashMap<String, Object> variables = new HashMap<>();
    User user = new User();
    user.id=594000L;
    user.username="gc";
    variables.put("user",user);
    //通过流程变量设置值
    runtimeService.startProcessInstanceByKey("holidays",variables);
    System.out.println("开启流程实例成功!");
}

流程变量

在分配任务负责人章节已经使用过流程变量了,这里要详细介绍下流程变量

流程变量在activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和activiti 结合时少不了流程变量,流程变量就是activiti在管理工作流时根据管理需要而设置的变量。 比如在请假流程流转时如果请假天数大于3天则由总经理审核,否则由人事直接审核,请假天 数就可以设置为流程变量,在流程流转时使用。

如果将pojo存储到流程变量中,必须实现序列化接口serializable,为了防止由于新增字段无 法反序列化,需要生成serialVersionUID。

1.流程变量作用域

流程变量的作用域默认是一个流程实例(processInstance),也可以是一个任务(task)或一个执行实例 (execution),这三个作用域流程实例的范围最大,可以称为global变量

global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。 Local变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。 Local变量名也可以和global变量名相同,没有影响。

2.流程变量使用方法

1)设置流程变量:在流程可视化插件中设置或者在生成的xml中设置

2)通过UEL表达式使用流程变量

  • 可以在assignee处设置UEL表达式,表达式的值为任务的负责人 比如:${assignee},assignee 就是一个流程变量名称
  • 在连线上设置UEL表达式,决定流程走向 比如:{price>=10000}和{price<10000}: price 就是一个流程变量名称,uel表达式结果类型为 布尔类型

使用Global (全局)变量控制流程

所谓全局变量其实就是在启动流程时设置的变量,前面我们设置都是全局变量

现在需要根据申请的请假天数来决定是否交由总经理审批,当请假天数小于等于3天时,部门负责人审核完成后直接交由认识部存档入库,当请假天数大于3天时,除了部门负责人审核外还需总经理审批,总经理审核完之后才交由认识部存档入库。

image-20250607233312583

image-20250607233344168

image-20250607233419641

启动流程时设置变量值

// 启动流程时设置变量值
@Test
public void startProcessInstance() {
    String processDefinitionId="holidays2:1:ce496e69-43ac-11f0-80b4-3cf011eabf6f";
    String processDefinitionKey="holidays2";
    //外部业务id(比如可以是填写的申请表id,或者申请人id,或者外部业务的流程id)
    String businessKey="formId_11991";
    RuntimeService runtimeService = processEngine.getRuntimeService();
    HashMap<String, Object> variables = new HashMap<>();
    variables.put("username", "gc");
    variables.put("num",10);
    // 通过流程变量设置值
    // runtimeService.startProcessInstanceByKey("holidays", variables);
    // ProcessInstance processInstance
    //         = runtimeService.startProcessInstanceById(processDefinitionId,businessKey,variables);
    //通过startProcessInstanceByKey开启流程实例(过程实例),使用的是最新版的流程定义版本
    ProcessInstance processInstance
            = runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);
    System.out.println("开启流程实例成功!");
    System.out.println("ProcessInstanceId:"+processInstance.getId());
    System.out.println("ProcessDefinitionId:"+processInstance.getProcessDefinitionId());
    System.out.println("ProcessDefinitionKey:"+processInstance.getProcessDefinitionKey());
    System.out.println("DeploymentId:"+processInstance.getDeploymentId());
    System.out.println("getBusinessKey:"+processInstance.getBusinessKey());
    System.out.println("getName:"+processInstance.getName());
    System.out.println("getActivityId():"+processInstance.getActivityId());
}

完成任务时设置变量值

//任务id 
String taskId = ""; 
TaskService taskService = processEngine.getTaskService(); 
Holiday holiday = new Holiday(); 
holiday.setNum(4); 
// 定义流程变量 
Map<String, Object> variables = new HashMap<String, Object>(); 
//变量名是holiday,变量值是holiday对象 
variables.put("holiday", holiday); 
taskService.complete(taskId, variables);

通过当前流程实例设置

//当前流程实例执行 id,通常设置为当前执行的流程实例 
  String executionId="2601"; 
   
  RuntimeService runtimeService = processEngine.getRuntimeService(); 
  Holiday holiday = new Holiday(); 
  holiday.setNum(3); 
   
  //通过流程实例 id设置流程变量 
  runtimeService.setVariable(executionId, "holiday", holiday); 
   
  //一次设置多个值  
  //runtimeService.setVariables(executionId, variables)

通过当前任务设置

//当前待办任务id 
String taskId="1404"; 

TaskService taskService = processEngine.getTaskService(); 
Holiday holiday = new Holiday();
holiday.setNum(3); 
//通过任务设置流程变量 
taskService.setVariable(taskId, "holiday", holiday); 
//一次设置多个值  
//taskService.setVariables(taskId, variables)

使用变量时影响的相关表

SELECT * FROM act_ru_variable #当前流程变量表
记录当前运行流程实例可使用的流程变量,包括 global和local变量
Id_:主键_
Type_:变量类型
Name_:变量名称
Execution_id_:所属流程实例执行id,global和local变量都存储
Proc_inst_id_:所属流程实例id,global和local变量都存储
Task_id_:所属任务id,local变量存储 _
Bytearray_:serializable类型变量存储对应act_ge_bytearray表的id
Double_:double类型变量值 Long_:long类型变量值
Text_:text类型变量值
SELECT * FROM act_hi_varinst #历史流程变量表 记录所有已创建的流程变量,包括 global和local变量 字段意义参考当前流程变量表。

使用local流程变量

1.任务办理时设置

任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无 法在当前流程实例使用,可以通过查询历史任务查询。

//任务id 
String taskId = ""; 

TaskService taskService = processEngine.getTaskService(); 
// 定义流程变量 
Map<String, Object> variables = new HashMap<String, Object>(); 
Holiday holiday = new Holiday (); 
holiday.setNum(3); 
// 定义流程变量 
Map<String, Object> variables = new HashMap<String, Object>(); 
//变量名是holiday,变量值是holiday对象 
variables.put("holiday", holiday); 
// 设置local变量,作用域为该任务 
taskService.setVariablesLocal(tasked, variables); 
taskService.complete(taskId);

2.通过当前任务设置

//当前待办任务id 
String taskId="1404"; 
TaskService taskService = processEngine.getTaskService(); 
Holiday holiday = new Holiday (); 
holiday.setNum(3); 
//通过任务设置流程变量 
taskService.setVariableLocal(taskId, "holiday", holiday); 
//一次设置多个值  
//taskService.setVariablesLocal(taskId, variables)

候选人(Candidate-users)

在流程定义中在任务结点的assignee固定设置任务负责人,在流程定义时将参与者固定设置 在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。 针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务

在流程图中任务节点的配置中设置candidate-users(候选人),多个候选人之间用逗号分开

image-20250608110130770

候选人办理组任务的主要步骤

第一步:候选人查询组任务,指定候选人,查询该候选人当前的待办任务,所有候选人都能查到该任务,候选人不能直接办理任务,需要候选人拾取组任务后,组任务变成个人任务,候选人就变成了该任务的负责人。

第二步:候选人拾取(claim)任务,将候选人的组任务,变成个人任务,此时候选人就变成了该任务的负责人。如果拾取后不想办理该任务? 需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。

第三步:查询个人任务,和之前的一样查询即可

第四步:办理个人任务,和之前一样完成办理即可

候选人查询组任务

taskCandidateUser(candidateUser)

@Test
public void queryGroupTaskList(){
    // 流程定义key
    String processDefinitionKey = "holidays2";
    // 任务候选人, activiti:candidateUsers="jack,bob,Jerry"
    String candidateUser = "jack";
    TaskService taskService = processEngine.getTaskService();
    List<Task> list=null;
    try {
        list= taskService.createTaskQuery()
                .processDefinitionKey(processDefinitionKey)
                .taskCandidateUser(candidateUser)
                .list();
    } catch (Exception e) {
        System.out.println("候选人名单不存在");
    }
    if (list==null){
        return;
    }
    for (Task task : list) {
        System.out.println("任务id:"+task.getId());
        System.out.println("任务名字:"+task.getName());
        System.out.println("任务负责人:"+task.getAssignee());  //负责人结果为空,只有拾取任务后才有值
        System.out.println("外部业务id:"+task.getBusinessKey());
        System.out.println("所属实例id:"+task.getProcessInstanceId());
    }

}

如果候选人没被security加载,会报错没有找到用户,解决办法可以暂时设置候选人到内存中,设置完后查询就不会报错了

//报错信息:Cause: org.springframework.security.core.userdetails.UsernameNotFoundException:

@Configuration
public class SecurityConfig {
    @Bean
    public UserDetailsService userDetailsService() {
       //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
       InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
       manager.createUser(User.withUsername("jack").password("123456").authorities("activiti").build());
       manager.createUser(User.withUsername("bob").password("123456").authorities("activiti").build());
       manager.createUser(User.withUsername("Jerry").password("123456").authorities("activiti").build());
       return manager;
    }
}

候选人拾取(claim)任务

taskService.claim(taskId, user)

将候选人的组任务,变成个人任务,此时候选人就变成了该任务的负责人,此时查询组任务将查不到

//拾取任务
@Test
public void claimTask(){
    TaskService taskService = processEngine.getTaskService();
    //要拾取的任务id
    String taskId = "600c90d3-441a-11f0-9be4-3cf011eabf6f";
    //任务候选人id
    String user = "jack";
    //拾取任务
    //即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)
    //校验该用户有没有拾取任务的资格
    Task task = taskService.createTaskQuery()//
            .taskId(taskId)
            .taskCandidateUser(user)//根据候选人查询
            .singleResult();
    if(task!=null){
        taskService.claim(taskId, user);
        System.out.println("任务拾取成功");
    }
}

任务负责人归还组任务

将任务负责人设置为null即可

如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人

// 归还组任务,由个人任务变为组任务,还可以进行任务交接
@Test
public void setAssigneeToGroupTask() {
    // 查询任务使用TaskService
    TaskService taskService = processEngine.getTaskService();
    // 当前待办任务
    String taskId = "600c90d3-441a-11f0-9be4-3cf011eabf6f";
    // 任务负责人
    String userId = "jack";

    // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
    Task task = taskService.createTaskQuery().taskId(taskId)
            .taskAssignee(userId).singleResult();
    if (task != null) {
        // 如果设置为null,归还组任务,该 任务没有负责人
        taskService.setAssignee(taskId, null);
        System.out.println("归还组任务成功");
    }
}

负责人任务交接

直接设置任务负责人即可

任务交接,任务负责人将任务交给其它候选人办理该任务

//交接任务给其他候选人
@Test
public void setAssigneeToCandidateUser() {
    // 查询任务使用TaskService
    TaskService taskService = processEngine.getTaskService();
    // 当前待办任务
    String taskId = "600c90d3-441a-11f0-9be4-3cf011eabf6f";
    // 任务负责人
    String userId = "jack";

    // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
    Task task = taskService.createTaskQuery().taskId(taskId)
            .taskAssignee(userId).singleResult();

    if (task != null) {
        // 将此任务交给其它候选人办理该 任务
        String candidateuser = "bob";
        // 根据候选人和组任务id查询,如果有记录说明该 候选人有资格拾取该任务,无法实现,因为拾取任务后组任务无法查询到
        // Task task2 = taskService.createTaskQuery().taskId(taskId)
        //         .taskCandidateUser(candidateuser).singleResult();
        taskService.setAssignee(taskId, candidateuser);
        System.out.println("交接任务成功");
    }
}

影响的数据库表

SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务,由于该任务当前是组任务,所有 assignee为空,当拾取任务后该字段就是拾取用户的id

SELECT * FROM act_ru_identitylink #任务参与者,记录当前参考任务用户或组,当前任务如果设置 了候选人,会向该表插入候选人记录,有几个候选就插入几个

于act_ru_identitylink对应的还有一张历史表act_hi_identitylink,向act_ru_identitylink插入记录的同 时也会向历史表插入记录。任务完成

网关(gateway)

之前使用流程变量已经可以实现分支判断了,但是里面还存在一些问题,例如num为空的情况,这时候流程就会出错,为了解决这个问题,就可以使用网关,网关可以保证整个流程分支的保底策略,从而避免流程执行分支判断时出现意外情况。

排他网关(Exclusive gateway)

排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程 执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支, 注意,排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行,如果所有分支条件都不是true,报错) ,如果不使用网关则流程直接结束

image-20250608141548897

并行网关(ParallelGateway)

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进 入和外出顺序流的:

  • fork分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
  • join汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通 过汇聚网关。

注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时, 网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。

与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。

image-20250608142223826

财务结算和入库是两个execution分支,在act_ru_execution表有两条记录分别是财务结算和入库, act_ru_execution还有一条记录表示该流程实例。 待财务结算和入库任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。 并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

包含网关(Inclusive gateway)

包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上 定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。

包含网关的功能是基于进入和外出顺序流的:

  • 分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
  • 汇聚: 所有并行分支到达包含网关,会进入等待状态直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在 汇聚之后,流程会穿过包含网关继续执行。

image-20250608143947453

企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要 进行附加项体检。

员工类型: 通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导,通过包含网关的每个分支的连线上设置condition条件。

总结

Activiti工作流引擎相当于一个service层的服务,它提供了对外的调用service接口,一个流程包含:

  1. 流程定义:就是使用BPMN工具来绘制流程图,包含里面的分支判断变量等,最后生成BPMN流程xml文件
  2. 流程部署:将定义好的流程xml文件上传到activiti服务中,然后调用接口RepositoryService的Deployment对象来进行解析和部署一个流程定义,引擎会自动操作数据库表进行数据持久化。
  3. 启动流程实例开启任务:通过调用接口runtimeService.startProcessInstance*()方法启动一个流程实例,在启动流程实例时应当对分支判断变量值进行赋值、对流程任务负责人进行赋值、对传入的外部业务id进行赋值等。
  4. 查询流程进展和当前任务:通过接口RuntimeService来查询流程和任务数据,这个适用于展示给前端的数据
  5. 完成任务:通过调用taskService.complete()方法来完成具体的任务

因为Activiti工作流引擎相当于一个service层的服务,所以我们还需要对它进一步封装让它对外提供http服务,最终形成一个独立的微服务供外部服务调用,当然还有一些特别的功能例如流程节点驳回这些功能,activiti框架并未提供,所以需要自己封装一些方法来实现节点的跳转。


Comment