SpringBoot + Activiti6
- 一、Activiti6简述
- 1、简述
- 2、接口
- 3、库表
- 二、创建BPMN业务流程模型
- 二、整合
- 1.POM依赖
- 2. bpmn20.xml部署
- 3.application.properties配置
- 4.关闭SpringSecurity权限配置。
- 5.调用服务
- 5.1 接口描述
- 5.2 接口实例
- 6、节点跳转
- 6.1、回退
- 6.2、任意跳转
- 三、问题
- 1、Could not erite JSON:lazy loading outside command context;
一、Activiti6简述
1、简述
activiti介绍 Activiti是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理,工作流,服务协作等领域的一个开源,灵活的,易扩展的可执行流程语言框架。
2、接口
- RepositoryService:提供一系列管理流程部署和流程定义的API。
- RuntimeService:在流程运行时对流程实例进行管理与控制。
- TaskService:对流程任务进行管理,例如任务提醒、任务完成和创建任务等。
- IdentityService:提供对流程角色数据进行管理的API,这些角色数据包括用户组、用户及它们之间的关系。
- ManagementService:提供对流程引擎进行管理和维护的服务。
- HistoryService:对流程的历史数据进行操作,包括查询、删除这些历史数据。
- FormService:表单服务。
3、库表
- act_ge_ 通用数据表,ge是general的缩写
- act_hi_ 历史数据表,hi是history的缩写,对应HistoryService接口
- act_id_ 身份数据表,id是identity的缩写,对应IdentityService接口
- act_re_ 流程存储表,re是repository的缩写,对应RepositoryService接口,存储流程部署和流程定义等静态数据
- act_ru_ 运行时数据表,ru是runtime的缩写,对应RuntimeService接口和TaskService接口,存储流程实例和用户任务等动态数据
二、创建BPMN业务流程模型
bpmn编辑方式很多,最后生成的文件也是有一定差异,比如idea的actiBPM、eclipse的Designer等。
尝试几个之后,创建bpmn文件后内容标签很乱,因此采用官方Activiti提供的流程设计器应用,最终生成xml文件。提供了三个war包,分别是activiti-admin.war、activiti-app.war、activiti-rest.war。如果只是画图的话,将activiti-app.war部署到Tomcat的webapps目录,启动Tomcat即可。
二、整合
SpringBoot部分就不在描述了,不论使用idea还是eclipse都一样,先直接创建一个SpringBoot项目,配置好数据库、连接池、持久层等即可。以下支展示流程引擎相关部分。
1.POM依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
2. bpmn20.xml部署
将创建出的.bpmn20.xml文件拷贝到项目文件夹/resources/processes下,可进行自动部署。若业务要求手动,也可以不添加此步骤,开发接口直接上传文件部署也可以。
3.application.properties配置
#是否每次都更新数据库
spring.activiti.database-schema-update=true
# 自动部署验证设置:true-开启(默认)、false-关闭
spring.activiti.check-process-definitions=true
spring.activiti.process-definition-location-prefix=classpath:/processes/ *.bpmn
#保存历史数据级别设置为full最高级别,便于历史数据的追溯
spring.activiti.history-level=full
spring.activiti.db-history-used=true
4.关闭SpringSecurity权限配置。
如未引入SpringSecurity权限,则需要关闭权限校验的自动配置加载:exclude = SecurityAutoConfiguration.class。如果就是使用的这个则无需操作此步骤。
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@MapperScan("net.cnki.mapper")
public class ActiviotiBoot6Application {
public static void main(String[] args) {
SpringApplication.run(ActiviotiBoot6Application.class, args);
}
}
5.调用服务
一下服用调用过程中,尽量保持与业务脱离,进行解耦。
5.1 接口描述
/**
* 引擎管理服务,和具体业务无关,主要是可以查询引擎配置,数据库,作业等
*/
@Autowired
private ManagementService managementService;
/**
* 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源
* 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程
*/
@Autowired
private RepositoryService repositoryService;
/**
* 运行时服务,可以处理所有正在运行状态的流程实例,任务等
* 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点
*/
@Autowired
private RuntimeService runtimeService;
/**
* 任务服务,用于管理,查询任务,例如:签收,办理,指派等
* 是用来可以用来领取,完成,查询任务列表功能的
*/
@Autowired
private TaskService taskService;
/**
* 唯一服务,可以管理,查询用户,组之间的关系
* 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息
*/
@Autowired
private IdentityService identityService;
/**
* 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等
* 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等
*/
@Autowired
private HistoryService historyService;
5.2 接口实例
1、流程部署,在3张表中产生数据
- act_ge_bytearray 流程资源文件(2条数据)
- act_re_deployment 流程部署(1条数据)
- act_re_procdef 流程实例(1条数据)
方式一:
Deployment deployment = repositoryService.createDeployment()
.name(processName)//为本次部署命名
.addClasspathResource("processes/"+ bpmnName +".bpmn")//添加流程规则文件
.addClasspathResource("processes/"+ bpmnName +".png")//添加流程规则图片
.deploy();//部署
方式二:
Deployment deployment = repositoryService.createDeployment()
.name(deployName)
.addInputStream(fileName,ipt)
.deploy();//部署
2、启动流程实例
关注2张表:
- act_ru_execution : 所有流程按照规则指定到活动节点时,都会产生一个对应的Execution
- act_ru_task : 只针对人工任务,针对Execution做的扩展信息描述
//一般使用时不会直接记录流程过程中的各种id,而是通过绑定的业务id(businessKey)处理
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);
3、执行流程节点
有两种情况,一是指定执行人,此时当前用户直接处理即可。二是指定角色,需要用户认领后再处理。此过程根据需要添加批注,便于记录过程中操作
//认领节点
taskService.claim(taskId,userId);
//完成节点
taskService.complete(taskId, variables);
4、添加批注
此方法有两步,先设置认证用户、再添加批注。
//设置认证用户
identityService.setAuthenticatedUserId(userId);
//添加批注
taskService.addComment(taskId,processInstanceId,message);
5、查询待处理节点
为了方便,此处使用了流程引擎的角色用户关联模块,同时包含当前流程参数的过滤条件、当前用户所属角色组以及自己所属的任务查询。
variables:启动流程实例时,对应的实例自定义参数,一般为业务相关参数。
assignee:当前用户信息,用户id或用户名均可。只要都统一就行
TaskQuery query = taskService.createTaskQuery();
variables.forEach((k,v) -> {if(!StringUtils.isEmpty(v)) query.processVariableValueEquals(k, v);});
List<Task> list = query.taskCandidateOrAssigned(assignee).includeProcessVariables().orderByTaskCreateTime().asc().list();
6、节点跳转
总有一些特别需求,不按套路出牌的。更加灵活的处理节点,还别说,这样情况不少。这是实现所需要的接口服务层。
/**
* 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源
* 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程
*/
@Autowired
private RepositoryService repositoryService;
/**
* 运行时服务,可以处理所有正在运行状态的流程实例,任务等
* 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点
*/
@Autowired
private RuntimeService runtimeService;
/**
* 任务服务,用于管理,查询任务,例如:签收,办理,指派等
* 是用来可以用来领取,完成,查询任务列表功能的
*/
@Autowired
private TaskService taskService;
/**
* 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等
* 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等
*/
@Autowired
private HistoryService historyService;
/**
* 唯一服务,可以管理,查询用户,组之间的关系
* 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息
*/
@Autowired
private IdentityService identityService;
6.1、回退
回退到指定历史节点
/**
* 回退到指定历史节点
* @param processInstanceId 流程实例ID
* @param targetTaskId 回跳任务ID,为null是默认选择最近的上一任务
* @param variables 撤回者处理当前任务参数
* @return
*/
Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);
@Override
public Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables) {
//获取流程实例
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (pi == null){//流程已结束
return false;
}
//对应实例的任务历史节点
List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().desc().list();
// 跳转的之前某一节点
HistoricTaskInstance targetTask = getTargetTask(htiList,targetTaskId);
// list里第一条代表当前任务
HistoricTaskInstance curTask = htiList.get(0);
if (targetTask == null){
return false;
}
targetTaskId = targetTask.getId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(targetTask.getProcessDefinitionId());
// 得到ActivityId,只有HistoricActivityInstance对象里才有此方法
String lastActivityId = getLastActivityId(targetTaskId,targetTask.getExecutionId());
// 得到上个节点的信息
FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivityId);
// 取得当前节点的信息
Execution execution = runtimeService.createExecutionQuery().executionId(curTask.getExecutionId()).singleResult();
FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(execution.getActivityId());
// 记录当前节点的原活动方向
List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());
// 清理活动方向
curFlowNode.getOutgoingFlows().clear();
// 建立新方向
List<SequenceFlow> newSequenceFlow = newSequenceFlowList(curFlowNode,lastFlowNode);
curFlowNode.setOutgoingFlows(newSequenceFlow);
// 添加批注并完成任务
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
commentAndComplete(processInstanceId,task.getId(),task.getAssignee(),variables,"撤回");
// 恢复原方向
curFlowNode.setOutgoingFlows(oriSequenceFlows);
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
// 设置执行人
if (nextTask != null) {
taskService.setAssignee(nextTask.getId(), targetTask.getAssignee());
}
return true;
}
6.2、任意跳转
可以跳转到本实例的任意节点-向前向后
/**
* 跳转到指定节点<br>
* 可以跳转到本实例的任意节点-向前向后
* @param processInstanceId 实例ID
* @param targetTaskId 任务ID,与运行时任务ID不同,为xml文件上的ID
* @param variables 本节点处理参数
* @return
*/
Boolean jumpSpecifiedNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);
@Override
public Boolean jumpSpecifiedNode(String processInstanceId, String targetTaskId, Map<String, Object> variables) {
//获取流程实例
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (pi == null){//流程已结束
return false;
}
//当前任务
Task curTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(curTask.getProcessDefinitionId());
//目标任务
Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
Optional<FlowElement> optional = flowElements.stream().filter(target -> targetTaskId.equals(target.getId())).findFirst();
if (!optional.isPresent()){//无指定任务
return false;
}
UserTask targetUserTask = (UserTask) optional.get();
// 获取目标节点的信息
FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(targetUserTask.getId());
// 取得当前节点的信息
Execution execution = runtimeService.createExecutionQuery().executionId(curTask.getExecutionId()).singleResult();
FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(execution.getActivityId());
// 记录当前节点的原活动方向
List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());
// 清理活动方向
curFlowNode.getOutgoingFlows().clear();
// 建立新方向
List<SequenceFlow> newSequenceFlow = newSequenceFlowList(curFlowNode,targetFlowNode);
curFlowNode.setOutgoingFlows(newSequenceFlow);
// 添加批注并完成任务
commentAndComplete(processInstanceId,curTask.getId(),curTask.getAssignee(),variables,"跳转");
// 恢复原方向
curFlowNode.setOutgoingFlows(oriSequenceFlows);
// 设置执行人
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (nextTask != null) {
taskService.setAssignee(nextTask.getId(), targetUserTask.getAssignee());
}
return true;
}
针对上边的两个跳转,提出一些不影响查看思路的公共部分。大体思路相同。
/**
* 添加批注并完成任务
* @param processInstanceId
* @param curTaskId 当前任务ID
* @param targetTaskAssignee
* @param variables
* @param message
*/
private void commentAndComplete(String processInstanceId,String curTaskId,String targetTaskAssignee,Map<String, Object> variables,String message){
identityService.setAuthenticatedUserId(targetTaskAssignee);
taskService.addComment(curTaskId,processInstanceId,message);
taskService.complete(curTaskId,variables);
}
/**
* 获取指定的目标历史任务
* @param htiList 历史任务列表
* @param targetTaskId 目标任务ID,为null是默认选择最近的上一任务
* @return
*/
private HistoricTaskInstance getTargetTask(List<HistoricTaskInstance> htiList,String targetTaskId){
HistoricTaskInstance targetTask = null;
if (targetTaskId == null){
targetTask = htiList.get(1);
} else {
Optional<HistoricTaskInstance> optional = htiList.stream().filter(hisTask -> targetTaskId.equals(hisTask.getId())).findFirst();
targetTask = optional.isPresent() ? optional.get() : null;
}
return targetTask;
}
/**
* 新建连线
* @param curFlowNode 当前节点
* @param targetFlowNode 目标节点
* @return
*/
private List<SequenceFlow> newSequenceFlowList(FlowNode curFlowNode,FlowNode targetFlowNode) {
List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newJumpFlow");
newSequenceFlow.setSourceFlowElement(curFlowNode);
newSequenceFlow.setTargetFlowElement(targetFlowNode);
newSequenceFlowList.add(newSequenceFlow);
return newSequenceFlowList;
}
/**
* 获取目标历史节点的活动ID
* @param targetTaskId 目标历史节点
* @param targetExecutionId 目标历史执行流ID
* @return
*/
private String getLastActivityId(String targetTaskId,String targetExecutionId){
List<HistoricActivityInstance> haiFinishedList = historyService.createHistoricActivityInstanceQuery().executionId(targetExecutionId).finished().list();
Optional<HistoricActivityInstance> optional = haiFinishedList.stream().filter(his -> targetTaskId.equals(his.getTaskId())).findFirst();
String lastActivityId = optional.isPresent() ? optional.get().getActivityId() : null;
return lastActivityId;
}
三、问题
1、Could not erite JSON:lazy loading outside command context;
返回数据json转化异常
自己测试的时候,如果直接流程引擎相关返回的对象数据直接返回,就会有json问题,因此一般都需要自己转化一下,不要直接使用task等。