camunda入门

Camunda

Camunda是一个工作流引擎,执行Bpmn2.0标准,因此依赖于基于bpmn的流程图(本质上是一个xml文件)

基本概念

1588901425191.png

ProcessDefinition

简单的认为就是画的流程图

ProcessInstance

流程图实例,就是根据某个流程图定义发起了一个新的流程,那么这个新的流程就是一个流程实例

StartEvent、EndEvent

流程的开始和结束节点

Task

任务,当流程流转到某个阶段,需要用户审核或者其他操作的时候,这个需要用户来完成的操作就是一个任务,除了用户任务之外,还有系统任务等其他任务

任务执行人

如果需要让某个用户执行某个任务,首先需要将任务分配给用户,一般有3种分配方式

  1. 直接指定,这里通过Assignee来直接指定某一个具体的用户(一般是用户ID或者唯一的用户名),支持表达式以支持动态指定
  2. 指定候选人,通过candidateUser来指定一系列候选人,如果是多个用户,通过,号分隔
  3. 指定候选组,通过candidateGroup来指定某一个组里面的所有用户(实际测试中,发现候选人和候选组是并集关系)

如果指定了候选人和候选组,那么并不意味着所有的候选人都需要执行任务,这些人首先需要进行一个认领的操作,一个任务只能由一个人认领,认领完成后才能执行任务,相对的,也可以取消认领

SequenceFlow

简而言之就是流程图的箭头,配合Gateway使用的时候,还可以在箭头上指定表达式,用于控制流向

流程变量

SequenceFlow的表达式依赖于流程变量,流程变量通过 流程启动、完成任务来传递

Gateway

网关,条件判断流程图流向,一般有Inclusive GatewayExclusive GatewayParallel Gateway

Exclusive Gateway

排他网关

1588899717890.png

排他网关可以有多个流出,但最终只能选择一条路径

Parallel Gateway

并行网关

1588899778691.png

并行网关可以有多个流出,并且每个流出都会被执行(忽略表达式),当每个流出都执行完毕之后,才进行下一步流转,并行网关的流入和流出的标识是相同的,例如

1588900246498.png

Inclusive Gateway

包含网关

1588899683807.png

包含网关等于并行网关和排他网关的组合,相对于并行网关,每个流出都会计算表达式,当每个流出都执行完毕之后,才会执行下一步流转,包含网关的流入和流出的标识是相同,例如:

1588900860271.png

流程图绘制工具

基本流程图

https://demo.bpmn.io/

camunda流程图

https://github.com/bpmn-io/bpmn-js-examples/tree/master/properties-panel

和Spring Boot集成

参考文档:https://docs.camunda.org/get-started/spring-boot/project-setup/

安装完毕后把绘制好的bpmn文件丢在resources目录下,应用启动后会自动识别,camunda还支持在application.properties对工作流进行配置,配置项如下: https://docs.camunda.org/manual/latest/user-guide/spring-boot-integration/configuration/#camunda-engine-properties

对表单的支持

一般发起一个流程或者用户进行审核时,依赖于一个表单,camunda和表单的关联,有3种形式

1588903181804.png

内置表单

内置表单缺点过于明显,实际情况基本不会使用

优点

  1. 方便

缺点

  1. 表单缺乏更深层次的校验
  2. 变更繁琐,维护困难,往往一个地方变更,其他地方也要变更
  3. 无法自定义样式

外联表单

外联表单通过formKey属性来关联,被关联的表单会储存于camunda自己的数据库表中,但实际操作中,我发现这个比较麻烦,所以可以用过自定义FormEngine来储存表单

优点

  1. 维护容易
  2. 自定义方便,可以通过拖拽等方式生成表单

缺点

  1. 当设计到和业务相关的表单控件(比如用户选择)需要一定量的开发工作,并且进行数据解析

业务表单

业务表单 通过 businessKey(基本就是业务表的ID)来关联,这样的话实际上表单和camunda是分开的,因此不存在通过camunda渲染表单的情况

优点

  1. 和业务无缝集成
  2. 无需针对表单开发

表单的渲染

StartEvent上的表单渲染

@Autowired
ProcessEngine engine ;

engine.getFormService().getRenderedStartForm(processDefinitionId, "testFormEngine")//返回一个String对象,一般情况下是一段html片段

Task上的表单渲染

@Autowired
ProcessEngine engine ;

engine.getFormService().getRenderedTaskForm(taskId, "testFormEngine")

基本使用

接口

ProcessEngine 核心接口,提供了工作流所有的操作的api

  1. RuntimeService 用于开启流程实例、删除流程实例、以及搜索流程实例等操作
  2. TaskService 用于用户任务的认领、完成、分发等操作
  3. IdentityService 用于提供身份认证以及管理用户和用户组
  4. HistoryService 用于查询历史流程实例、历史任务以及历史流程变量等
  5. FormService 用户内外联表单的渲染、通过提交表单开启流程实例、通过提交表单完成任务等

启动流程

启动流程一般有两种方式

通过表单启动

如果StartEvent使用了内置或者外联表单,那么应该通过表单来启动一个流程,例如:

年龄:
@Autowired
ProcessEngine engine ;

Map<String,Object> map;//这里的map是表单的键值对,在上述的表单中,这里应该是map.put("age","inputValue")
engine.getFormService().submitStartForm(processDefinitionId, map);//这里的map就是流程变量,后面可以通过${age}来获取传递的年龄

通过流程定义启动

如果跟业务表单相关联,那么应该通过流程定义来启动,例如:

@Autowired
ProcessEngine engine;

engine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, businessKey)//这里的business是业务表单的ID

流程变量

实际的使用过程中,流程变量使用很频繁,例如流程该怎么走,外联表单的数据传递等,都需要使用到流程变量,流程变量可以在启动流程的时候传入,也可以在完成任务的时候传入,除非特别指定变量为本地变量,否则传递的变量都是整个流程实例程共享,在上面的流程启动中,传递了一个map对象,那么这个map里面的值就会被视作为流程变量,下面流程中,如果填写的年龄小于16岁,便需要填写xxx

1588906568148.png

完成一个任务

如果使用了业务表单,那么应该通过 TaskService来完成

TaskService ts = engine.getTaskService();
Task task = engine.getTaskService().createTaskQuery().taskId(taskId).singleResult();
ts.createComment(taskId, task.getProcessInstanceId(), comment);
ts.complete(vo.getTaskId(), Map.of(task.getId(), status));

如果使用了外联表单,那么应该通过 FormService来完成

engine.getFormService().submitTaskForm(taskId, map);

查询需要认领的任务

List<String> groups = this.userGroupManager.getUserGroups(userId);
List<Task> tasks = engine.getTaskService().createTaskQuery().processDefinitionKey(definitionKey)
        .taskCandidateGroupIn(groups).list();
tasks.addAll(engine.getTaskService().createTaskQuery()
        .taskCandidateUser(userId).list());

认领任务

ProcessEngine engine;
engine.getTaskService().claim(taskId, userId);

取消认领任务

engine.getTaskService().claim(taskId, null);

删除任务

engine.getTaskService().deleteTask(taskId, deleteReason);

当前需要处理的任务

List<HistoricTaskInstance> instances = engine.getHistoryService().createHistoricTaskInstanceQuery()
        .taskAssignee('用户ID').unfinished().orderByHistoricActivityInstanceStartTime().asc().list();

查询处理过的历史任务

List<HistoricTaskInstance> instances = engine.getHistoryService().createHistoricTaskInstanceQuery()
				.taskAssignee('用户ID').finished().orderByHistoricActivityInstanceStartTime().desc().list();

查询某个流程下任务处理记录

HistoryService hs = engine.getHistoryService();
List<HistoricTaskInstance> list = hs.createHistoricTaskInstanceQuery().processInstanceId(`流程实例ID`)
        .orderByHistoricActivityInstanceStartTime().asc().list();
for (HistoricTaskInstance ins : list) {
    if (ins.getAssignee() == null)//任务还没有被认领
        continue;
    System.out.println(ins.getStartTime());//任务开始时间
    System.out.println(ins.getEndTime());//用户审核完毕时间
    System.out.println(ins.getAssignee());//审核人
    System.out.println(ins.getName());//任务名称
    
    HistoricVariableInstance variable = engine.getHistoryService().createHistoricVariableInstanceQuery()
            .variableName(ins.getId()).singleResult();

    if (variable != null) {
        System.out.println(variable.getValue().toString());//查询审核人对任务的审核结果
    }

    List<String> comments = engine.getTaskService().getTaskComments(ins.getId()).stream().map(Comment::getFullMessage)
            .collect(Collectors.toList());
    System.out.println(comments); //查询审核人对任务处理的评论
}

demo

https://github.com/mhlx/camunda_demo

下一篇

从0开始搭建一个ss服务

内容受到密码保护
06