Skip to main content

Activiti7的核心详解


Activiti7的核心详解

流程定义

流程定义是线下按照bpmn2.0标准去描述业务流程,通常使用activiti-explorer(web控制台)或activiti-eclipse-designer插件对业务流程进行建模,这两种方式都遵循bpmn2.0标准。

使用activiti-desinger设计业务流程,会生成.bpmn文件

.png图片需手动生成,在IDEA中步骤如下:

  • 1)将holiday.bpmn文件改为扩展名xml的文件名称:holiday.xml
  • 2)在holiday.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer...
  • 3)点击Export To File的小图标,选择好保存图片的位置

  • 4)中文乱码的解决

打开IDEA安装路径,找到如下的安装目录,在idea64.exe.vmoptions文件的最后一行追加一条命令:-Dfile.encoding=UTF-8

-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\bin\JetbrainsCrack-2.8-release-enc.jar
-Dfile.encoding=UTF-8

一定注意,不要有空格,否则重启IDEA时会打不开,然后重启IDEA,把原来的png图片删掉,再重新生成,即可解决乱码问题

1.什么是流程定义部署

将线下定义的流程部署到activiti数据库中,这就是流程定义部署,通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。

单个文件部署方式:

/**
 * 流程定义的部署
 * activiti表有哪些?
 * act_re_deployment  流程定义部署表,记录流程部署信息
 * act_re_procdef     流程定义表,记录流程定义信息
 * act_ge_bytearray   资源表(bpmn文件及png文件)
 */
@Test
public void createDeploy() { 
    RepositoryService repositoryService = processEngine.getRepositoryService();

    Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("diagram/holiday.bpmn")//添加bpmn资源
            .addClasspathResource("diagram/holiday.png")
            .name("请假申请单流程")
            .deploy(); 
    log.info("流程部署id:" + deployment.getName());
    log.info("流程部署名称:" + deployment.getId());
    }

2.流程定义查询

@Test
public void queryProcessDefinition() { 
    String processDefinitionKey = "holiday"; 
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //查询流程定义
    ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
 
    List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinitionKey)
            .orderByProcessDefinitionVersion().desc().list();

    for (ProcessDefinition processDefinition : list) {
        log.info("------------------------");
        log.info("流  程  部  署id:" + processDefinition.getDeploymentId());
        log.info("流程定义id:" + processDefinition.getId());
        log.info("流程定义名称:" + processDefinition.getName());
        log.info("流程定义key:" + processDefinition.getKey());
        log.info("流程定义版本:" + processDefinition.getVersion());
    }
}

3.流程定义删除

/**
 * 删除已经部署成功的流程定义
 * 背后影响的表:
 * act_ge_bytearray
 * act_re_deployment
 * act_re_procdef
 */
@Test
public void deleteDeployment() {
    String deploymentId = "a2833cf7-10bb-11ea-9ac9-00155d65d6c0";
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //删除流程定义,如果该流程定义已有流程实例启动则删除时出错
//        repositoryService.deleteDeployment(deploymentId);

    //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
    repositoryService.deleteDeployment(deploymentId, true);
}

流程实例

1.什么是流程实例

参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例。

2.启动流程实例

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

@Test
public void startProcessInstance() {
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");

    log.info("流程定义ID:" + processInstance.getProcessDefinitionId());
    log.info("流程实例ID:" + processInstance.getId());
}

3.Businesskey(业务标识)

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

Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。 比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息。

@Test
public void startProcessInstance() {
    RuntimeService runtimeService = processEngine.getRuntimeService();
    //启动流程实例,同时还要指定业务标识businessKey  它本身就是请假单的id
    //第一个参数:是指流程定义key
    //第二个参数:业务标识businessKey
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday", "1001");

    String businessKey = processInstance.getBusinessKey();
    System.out.println("businessKey:" + businessKey);
}

4.操作数据库表

ct_ru_execution #流程实例执行表,记录当前流程实例的执行情况,一个流程实例运行完成,此表中与流程实例相关的记录删除。

ct_ru_task #任务执行表,记录当前执行的任务,启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。

ct_hi_procinst #流程实例历史表,流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。

ct_hi_taskinst #任务历史表,记录所有任务,开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。

ct_hi_actinst #活动历史表,记录所有活动

5.查询流程实例

@Test
public void queryProcessInstance() { 
    String processDefinitionKey = "holiday"; 
    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());
    }
}

个人任务

1.分配任务负责人

  • 1)固定分配

在进行业务流程建模时指定固定的任务负责人,在properties视图中,填写Assignee项为任务负责人。

由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照bpmn的配置去分配任务负责人。

  • 2)表达式分配

Activiti使用UEL(统一表达式语言)表达式,activiti支持两个UEL表达式:UEL-value和UEL-method

assignee这个变量是activiti的一个流程变量

UEL-method方式如下:

ldapService是spring容器的一个bean,findManagerForEmployee是该bean的一个方法,emp是activiti流程变量,emp作为参数传到ldapService.findManagerForEmployee方法中。

其它:表达式支持解析基础类型、bean、list、array和map,也可作为条件判断。如下:${order.price > 100 && order.price < 250}

使用流程变量分配任务

@Test
public void assignVariables() {
    RuntimeService runtimeService = processEngine.getRuntimeService();

    //设置assignee的取值,用户可以在界面上设置流程的执行人
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("assignee0", "张三");
    map.put("assignee1", "李四");
    map.put("assignee2", "王五");
    
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday2", map);
 System.out.println(processInstance.getProcessInstanceId());
}
  • 3)监听器分配

任务监听器是发生对应的任务相关事件时执行自定义java逻辑或表达式

  • Create:任务创建后触发
  • Assignment:任务分配后触发
  • Delete:任务完成后触发
  • All:所有事件发生都触发

定义任务监听类,且类必须实现org.activiti.engine.delegate.TaskListener接口

public class MyTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.setAssignee("赵六");
    }
}

  • 4)查询任务
@Test
public void findPersonalTaskList() { 
    String processDefinitionKey = "holiday2"; 
    String assignee = "赵六"; 
    TaskService taskService = processEngine.getTaskService();

    List<Task> taskList = taskService.createTaskQuery()
            .processDefinitionKey(processDefinitionKey)
            .includeProcessVariables().taskAssignee(assignee).list();

    for (Task task : taskList) {
        System.out.println("--------------------------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}

流程变量

1.什么是流程变量

流程变量就是activiti在管理工作流时根据管理需要而设置的变量。

比如在请假流程流转时如果请假天数大于3天则由总经理审核,否则由人事直接审核,请假天数就可以设置为流程变量,在流程流转时使用。

2.流程变量作用域

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

3.流程变量的使用方法

  • 1)设置流程变量

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

    • 可以在assignee处设置UEL表达式,表达式的值为任务的负责人,比如:${assignee},assignee就是一个流程变量名称,Activiti获取UEL表达式的值,即流程变量assignee的值,将assignee的值作为任务的负责人进行任务分配
    • 可以在连线上设置UEL表达式,决定流程走向,比如:和{price<10000}:price就是一个流程变量名称,uel表达式结果类型为布尔类型,如果UEL表达式是true,要决定流程执行走向。

4.使用Global变量控制流程

员工创建请假申请单,由部门经理审核,部门经理审核通过后请假3天及以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过再由人事经理存档。

请假天数大于3连线条件

  • 1)启动流程时设置

在启动流程时设置流程变量,变量的作用域是整个流程实例。通过map<key,value>设置流程变量,map中可以设置多个变量,这个key就是流程变量的名字。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Holiday implements Serializable {
    //必须实现序列化接口serializable,为了防止由于新增字段无法反序列化,需要生成serialVersionUID
    private static final long serialVersionUID = 5707634407289856169L;
    private Integer id;
    private String holidayName;//申请人的名字
    private Date beginDate;//开始时间
    private Date endDate;//结束日期
    private Float num;//请假天数
    private String reason;//事由
    private String type;//请假类型

}



/**
 * 启动流程时设置流程变量
 * act_ge_bytearray
 * act_ru_variable
 */
@Test
public void startProcessInstance() {
    String processDefinitionKey = "holiday4";

    Holiday holiday = new Holiday();
    holiday.setNum(5F);

    //定义流程变量
    Map<String, Object> variables = new HashMap<>();
    variables.put("holiday", holiday);

    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
    System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
}
  • 2)任务办理时设置

在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。

@Test
public void completeTask() {
    TaskService taskService = processEngine.getTaskService();

    String key = "holiday4";
    String assignee = "张三"; 
    Task task = taskService.createTaskQuery().processDefinitionKey(key).
            taskAssignee(assignee).singleResult();

    Holiday holiday = new Holiday();
    holiday.setNum(4F);
    //初始化一些参数
    Map<String, Object> map = new HashMap<>();
    map.put("holiday", holiday);
 
    if (task != null) {
        taskService.complete(task.getId(), map);//完成任务时,设置流程变量的值
        System.out.println("任务执行完毕");
    }
}
  • 3)通过当前流程实例设置
@Test
public void setGlobalVariableByExecutionId() {
    String executionId = "f789207c-0aa2-11ea-9a53-00155d65d6c0";

    RuntimeService runtimeService = processEngine.getRuntimeService();
    Holiday holiday = new Holiday();
    holiday.setNum(3F); 
    runtimeService.setVariable(executionId, "holiday", holiday);

    System.out.println(runtimeService.getVariable(executionId, "holiday"));
}

executionId必须当前未结束流程实例的执行id,通常此id设置流程实例的id。也可以通过runtimeService.getVariable()获取流程变量

  • 4)通过当前任务设置
@Test
public void setGlobalVariableByTaskId() {
    String taskId = "4d47161e-0aa4-11ea-aea8-00155d65d6c0";

    TaskService taskService = processEngine.getTaskService();
    Holiday holiday = new Holiday();
    holiday.setNum(5F); 
    Map<String, Object> variables = new HashMap<>();
    variables.put("holiday", holiday);

    taskService.setVariable(taskId, "holiday", holiday);

    System.out.println(taskService.getVariable(taskId, "holiday"));
}

5.设置local流程变量

  • 1)任务办理时设置

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

@Test
public void completeTaskLocal() {
    String taskId = "676c8a5d-0b35-11ea-a44a-00155d65d6c0";

    Map<String, Object> variables = new HashMap<>();
    Holiday holiday = new Holiday();
    holiday.setNum(3F);
    variables.put("holiday", holiday);

    TaskService taskService = processEngine.getTaskService();
    //设置local变量,作用域为该任务
    taskService.setVariablesLocal(taskId, variables);
    taskService.complete(taskId);
}

设置作用域为任务的local变量,每个任务可以设置同名的变量,互不影响。

6.查询历史流程变量

@Test
public void queryHistoricLocalVariables() {
    HistoryService historyService = processEngine.getHistoryService();

    HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery(); 
    List<HistoricTaskInstance> list = historicTaskInstanceQuery.includeTaskLocalVariables()
            .finished().list();

    for (HistoricTaskInstance hti : list) {
        System.out.println("============================");
        System.out.println("任务id:" + hti.getId());
        System.out.println("任务名称:" + hti.getName());
        System.out.println("任务负责人:" + hti.getAssignee());
        System.out.println("任务local变量:" + hti.getTaskLocalVariables());
    }
}

组任务

1.Candidate-users候选人

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

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

2.办理组任务

  • 1)用户查询组任务
@Test
public void findGroupTaskList() {
    String processDefinitionKey = "holiday4";
    String candidateUser = "李四";
    TaskService taskService = processEngine.getTaskService();

    List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey)
            .taskCandidateUser(candidateUser).list();//根据候选人查询

    for (Task task : taskList) {
        System.out.println("---------------------");
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}
  • 2)用户拾取组任务
/**
 * 用户拾取组任务,该任务变为自己的个人任务
 */
@Test
public void claimTask() {
    TaskService taskService = processEngine.getTaskService();

    String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
    String userId = "李四";

    //校验该用户有没有拾取任务的资格
    Task task = taskService.createTaskQuery().taskId(taskId)
            .taskCandidateUser(userId).singleResult();//根据候选人查询

    if (task != null) {
        taskService.claim(taskId, userId);
        System.out.println("任务拾取成功");
    }
}
  • 3)用户办理个人任务
@Test
public void completeTask() {
    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery().processDefinitionKey("holiday4")
            .taskAssignee("张三").singleResult();

    if (task != null) {
        taskService.complete(task.getId());
        System.out.println("用户任务执行完毕...");
    }
}
  • 4)归还组任务

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

/**
 * 归还组任务,由个人任务变为组任务,还可以进行任务交接
 */
@Test
public void setAssigneeToGroupTask() {
    TaskService taskService = processEngine.getTaskService();
    String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
    String userId = "李四";

    //校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
    Task task = taskService.createTaskQuery().taskId(taskId)
            .taskAssignee(userId).singleResult();
    if (task != null) {
        //如果设置为null,归还组任务,该任务没有负责人
        taskService.setAssignee(taskId, null);
    }
}
  • 5)任务交接
/**
 * 任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人
 */
@Test
public void setAssigneeToCandidateUser() {
    TaskService taskService = processEngine.getTaskService();
    String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
    String userId = "李四";

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

    if (task != null) {
        //将此任务交给其它候选人办理该任务
        String candidateUser = "张三";
        //根据候选人和组任务id查询,如果有记录说明该候选人有资格拾取该任务
        Task task1 = taskService.createTaskQuery().taskCandidateUser(candidateUser).singleResult();
        if (task1 != null) {
            taskService.setAssignee(taskId, candidateUser);
        }
    }
}

网关

1.排他网关

  • 1)什么是排他网关

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

  • 2)流程定义

在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断请假天数是否大于3天,另一条是判断请假天数是否小于等于3天。

  • 3)测试

流程定义部署-->启动流程设置流程变量-->执行任务

@Test
public void completeTask() {
    TaskService taskService = processEngine.getTaskService();

    Task task = taskService.createTaskQuery().processDefinitionKey("holiday5_1")
            .taskAssignee("lisi").singleResult();
    if (task != null) {
        taskService.complete(task.getId());
        System.out.println("用户任务执行完毕...");
    }
}

2.并行网关

  • 1)什么是并行网关

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

  • fork分支

    并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。

  • join汇聚

    所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后,流程就会通过汇聚网关。

如果同一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略

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

  • 2)流程定义

  • 3)测试

流程定义部署-->启动流程设置流程变量-->执行任务

@Test
public void completeTask() {
    TaskService taskService = processEngine.getTaskService();

    Task task = taskService.createTaskQuery().processDefinitionKey("holidayParallel")
            .taskAssignee("zhangsan").singleResult();
    if (task != null) {
        taskService.complete(task.getId());
        System.out.println("用户任务执行完毕...");
    }
}

3.包含网关

  • 1)什么是包含网关

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

  • 分支

    所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行,会为每个顺序流创建一个分支

  • 汇聚

    所有并行分支到达包含网关,会进入等待状态,直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网关继续执行。

  • 2)流程定义

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

员工类型:通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导

  • 3)测试