springboot Activiti6
没有历史包袱建议使用Activiti7
activiti的使用分为两种:
1.仅使用activiti做某几个业务流的展示或者审批。这种只需要activiti的部分功能。
2.流程处理人待处理通知,看到待我处理流程列表,整体业务就是BPM这样的系统。
流程建模
需要下载设计工具Activiti6.0 UI
IDEA上的插件做的有问题,显示下BPMN图还行,设计还是老实用activiti团队提供的工具吧。
https://github.com/Activiti/Activiti/releases/download/activiti-6.0.0/activiti-6.0.0.zip
运行设计工具
解压部署在Tomcat或者weblogic等支持servlet的web服务器下(要求JDK >=7)
需要设置JAVA_HOME环境变量
用JDK1.8去运行
。
JDK11环境下运行会报未找到类定义的异常。
访问路径:http://localhost:8080/activiti-app
username:admin
password :test
登录页
流程图效果
BPMN2.0构件
起点
结束
活动
流程线
网关
排它网关:条件判断分支
并行网关:启动或者合并几个并行任务的节点。
其中flow condition里面:
${} 是EL表达式
audit是程序里面的variable变量
实际上activiti里面的EL表达式只是EL的子集,EL本身很强大我们并不需要使用到它的全部功能。
变量设置
每个流程实例都需要并使用数据来执行它存在的步骤。在 Activiti中,这些数据称为变量,它们存储在数据库中。变量可用于表达式(例如在独占网关中选择正确的传出序列流)、调用外部服务时的 java服务任务(例如提供输入或存储服务调用的结果)等。
流程实例可以有变量(称为流程变量),但也可以有执行(这是流程处于活动状态的特定指针)和用户任务可以有变量。一个流程实例可以有任意数量的变量。每个变量都存储在ACT_RU_VARIABLE数据库表中的一行中。
一般情况下我们使用RuntimeService和TaskService设置和获取变量,更多可能是在TaskService上使用。
Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);
变量通常用于Java 委托、表达式、执行或任务侦听器、脚本等。在这些构造中,当前执行或任务对象可用,并可用于变量设置和/或检索。最简单的方法是这些:
execution.getVariables();
execution.getVariables(Collection<String> variableNames);
execution.getVariable(String variableName);
execution.setVariables(Map<String, object> variables);
execution.setVariable(String variableName, Object value);
请注意,带有local的变体也可用于上述所有内容。
由于历史(和向后兼容的原因),在执行上述任何调用时,实际上所有变量都将从数据库中获取。
画不同拐角的流程线
增加活动点
导出BPMN2.0文件
流程开发
导入BPMN2.0文件
部署BPMN
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/expense-account-process.bpmn20.xml")
.deploy();
依赖
implementation 'org.activiti:activiti-spring-boot-starter-basic:6.0.0'
配置
数据源配置
一般情况下都是引用spring.datasource的实例,所以不需要单独配置
activiti配置
spring:
activiti:
database-schema-update: "true"
check-process-definitions: true
jpa-enabled: false
# process-definition-location-prefix: "classpath:/processes/"
# process-definition-location-suffixes: Arrays.asList("**.bpmn20.xml", "**.bpmn");
表结构
解决依赖冲突
需要排除一个配置,因为依赖冲突。springboot application启动类修改
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
@MapperScan("cn.activiti.*.mapper")
public class DemoApplication
确定前端流程每个节点需要输出的信息
- 开始时间
- 完成时间
- 节点名称
- 执行任务候选人
- 实际执行人
- 评语/注释
- 附件
代码实践
开启一个流程
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(ProcessID,note.getId(),variables);
挂起一个运行中流程
runtimeService.suspendProcessInstanceById(processInstance.getId());
删除一个运行中流程
runtimeService.deleteProcessInstance(processInstance.getId(), "删除流程实例的原因");
查询Task
// 通过业务key查询Task
Task task = taskService.createTaskQuery().processInstanceBusinessKey(business).singleResult();
// 通过流程实例ID查询Task
// Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
完成task,行进到下一节点
设置受理人,完成此节点
taskService.setAssignee(task.getId(), "受理人");
taskService.complete(task.getId());
添加描述/评论
taskService.addComment(task.getId(), processInstanceId, "messageType", "我是一段描述/评论文案");
带条件行进
// 审核拒绝
Map<String, Object> variables = new LinkedHashMap<>();
variables.put("audit", "reject");
taskService.setAssignee(task.getId(), "受理人");
taskService.complete(task.getId(), variables);
任务开始的时候节点的startTime就被自动赋值表示任务开始进行,当前节点被complete之后endTime就会被赋值,代表任务处理结束。在处理中endTime是null。
指定节点处理候选人/候选部门
taskService.addCandidateGroup(task.getId(), "指定审核候选部门");
taskService.addCandidateUser(task.getId(), "指定审核候选人");
userTask里面的候选人最好不要直接存UID,要根据业务场景来存入角色、部门职位ID等可以代表某一类人的字段,
因为大多数系统都会存在人员变动的情况,所以直接存入某人的UID,进行到此节点的时候会出现此人已经不在了从而无法进行节点的审核了。在这个场景下即便你存入多个候选人极端情况也是可能会碰到节点无人可以审核了。
查询业务流程运转历史的图片
public void watchProcessActivitiedImage(OutputStream outputStream, String processInstanceId) {
try {
HistoricProcessInstance historicProcessInstance = processEngine.getHistoryService()
.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
List<HistoricActivityInstance> historicActivityInstanceList = processEngine.getHistoryService()
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceId()
.asc()
.list();
BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(historicProcessInstance.getProcessDefinitionId());
List<Process> processList = bpmnModel.getProcesses();
if (processList != null && processList.size() > 0) {
Process process = processList.get(0);
Map<String, FlowElement> flowElementMap = process.getFlowElementMap();
if (flowElementMap != null) {
for (FlowElement flowElement : flowElementMap.values()) {
if (flowElement != null && flowElement instanceof SequenceFlow) {
String condition = ((SequenceFlow) flowElement).getConditionExpression();
flowElement.setDocumentation(getFlowElementName(condition));
}
}
}
}
// 获取已经流转的线
List<String> flowIds = this.getExecutedFlows(bpmnModel, historicActivityInstanceList);
// 构造已执行的流程节点ID集合
List<String> executedActivityIds = new ArrayList<String>();
for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
executedActivityIds.add(activityInstance.getActivityId());
}
//获取流程图信息
DefaultProcessDiagramGenerator generator = new DefaultProcessDiagramGenerator();
InputStream inputStream = generator.generateDiagram(bpmnModel, "png", executedActivityIds, flowIds, "Songti", "Songti", "Songti", null, 2.0);
try {
FileCopyUtils.copy(inputStream, outputStream);
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
效果
查看流程历史包括下一节点
显示下一节点是当你开始处理部门领导审核的时候在前端流程上就可以看到一下步是会计打钱,相应的会计打钱的处理候选人也会显示出来。总之就是为了方便业务流程的进行。
怎么在分支路线确定下一节点应该是哪一个呢?
我这里采用的方法是:在BPMN模型里面放置标识,然后在程序里识别。
具体操作是在部门领导审核的排它网关的审核通过流线线里识别“approve”,在会计打钱UserTask里面document字段识别带$默认 $字符。
条件分支流程线的document会被EL表达式的approve覆盖掉,所以我们的判断如下:
if(sequenceFlow.getDocumentation().indexOf("$默认$") >= 0 || sequenceFlow.getDocumentation().indexOf("approve") >= 0) {//判断跳转条件是否成立
resultTask = sequenceFlow.getTargetFlowElement();//条件成立 返回节点对象
}
见代码实现:List<ProcessElement> watchProcessWithNextStep(String processInstanceId);
测试流程
开始报销流程
http://localhost:8080/startOneProcess
{“businessKey”:“bae690e9-1887-4df3-924b-85bc6e5161c7”,“id”:“5”,“ProcessInstanceId”:“5”}
提交报销
http://localhost:8080/submit?businessKey=bae690e9-1887-4df3-924b-85bc6e5161c7
审核报销
http://localhost:8080/audit?businessKey=bae690e9-1887-4df3-924b-85bc6e5161c7&approve=true
打钱
http://localhost:8080/settlement?businessKey=bae690e9-1887-4df3-924b-85bc6e5161c7&approve=true
查看流程
http://localhost:8080/watchHistory?processInstanceId=5
没有做按startTime排序,需要后端处理排序可以自己加一下排序代码
[
{
"id":"startEvent1",
"name":"startEvent",
"startTime":"2021-09-18T16:51:05.464+00:00",
"endTime":"2021-09-18T16:51:05.465+00:00",
"assignee":null,
"candidates":null,
"comment":null,
"variables":null
},
{
"id":"submit",
"name":"?????",
"startTime":"2021-09-18T16:51:05.466+00:00",
"endTime":"2021-09-18T16:51:24.976+00:00",
"assignee":null,
"candidates":null,
"comment":null,
"variables":null
},
{
"id":"audit",
"name":"??????",
"startTime":"2021-09-18T16:51:24.976+00:00",
"endTime":"2021-09-18T16:51:31.260+00:00",
"assignee":"????Manager",
"candidates":null,
"comment":null,
"variables":null
},
{
"id":"sid-9908E70E-4B92-477F-9C86-71F78A856504",
"name":"exclusiveGateway",
"startTime":"2021-09-18T16:51:31.260+00:00",
"endTime":"2021-09-18T16:51:31.265+00:00",
"assignee":null,
"candidates":null,
"comment":null,
"variables":null
},
{
"id":"settlement",
"name":"????",
"startTime":"2021-09-18T16:51:31.266+00:00",
"endTime":"2021-09-18T16:51:37.410+00:00",
"assignee":"????",
"candidates":null,
"comment":null,
"variables":null
},
{
"id":"endEvent1",
"name":"endEvent",
"startTime":"2021-09-18T16:51:37.410+00:00",
"endTime":"2021-09-18T16:51:37.410+00:00",
"assignee":null,
"candidates":null,
"comment":null,
"variables":null
}
]
代码仓库
https://gitee.com/whatitis/springboot-activiti6-demo
实践指南
userTask里面的候选人最好不要直接存UID,要根据业务场景来存入角色、部门职位ID等可以代表某一类人的字段,
因为大多数系统都会存在人员变动的情况,所以直接存入某人的UID,进行到此节点的时候会出现此人已经不在了从而无法进行节点的审核了。在这个场景下即便你存入多个候选人极端情况也是可能会碰到节点无人可以审核了。
因为候选人的可变性,我们不要存入具体某几个人,而执行人是已经流转过的记录是不可变的,即便具体某个节点的执行人在将来某个时间在系统内被删除了,这个业务流程节点的记录依然不能变动,这样的话执行人的信息要明确,清晰的存放到业务流执行人字段里,哪怕后来这个人在系统里不存在了,单凭存入到流程节点执行人字段里面的描述信息也能让人知道具体执行人的信息,从而知道这个干系人是谁。建议可以存入:用户主键,用户账号,这些系统内唯一的、不可变的数据。
如果系统的用户删除不是逻辑删除的话为了不影响到业务流记录的查看用户名字可能也要留存下来,但是有一点是用户名字的变更要考虑到。
userTask都有startTime和endTime,当前task在completed了endTime就会被设置,然后下一个task的startTime也会被设置了。