SpringBoot + Activiti6简单教程包含任意跳转节点
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 接口描述
/*** 引擎管理服务,和具体业务无关,主要是可以查询引擎配置,数据库,作业等*/@Autowiredprivate ManagementService managementService;/*** 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源* 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程*/@Autowiredprivate RepositoryService repositoryService;/*** 运行时服务,可以处理所有正在运行状态的流程实例,任务等* 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点*/@Autowiredprivate RuntimeService runtimeService;/*** 任务服务,用于管理,查询任务,例如:签收,办理,指派等* 是用来可以用来领取,完成,查询任务列表功能的*/@Autowiredprivate TaskService taskService;/*** 唯一服务,可以管理,查询用户,组之间的关系* 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息*/@Autowiredprivate IdentityService identityService;/*** 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等* 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等*/@Autowiredprivate 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、节点跳转
总有一些特别需求,不按套路出牌的。更加灵活的处理节点,还别说,这样情况不少。这是实现所需要的接口服务层。
/*** 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源* 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程*/@Autowiredprivate RepositoryService repositoryService;/*** 运行时服务,可以处理所有正在运行状态的流程实例,任务等* 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点*/@Autowiredprivate RuntimeService runtimeService;/*** 任务服务,用于管理,查询任务,例如:签收,办理,指派等* 是用来可以用来领取,完成,查询任务列表功能的*/@Autowiredprivate TaskService taskService;/*** 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等* 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等*/@Autowiredprivate HistoryService historyService;/*** 唯一服务,可以管理,查询用户,组之间的关系* 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息*/@Autowiredprivate IdentityService identityService;
6.1、回退
回退到指定历史节点
/*** 回退到指定历史节点* @param processInstanceId 流程实例ID* @param targetTaskId 回跳任务ID,为null是默认选择最近的上一任务* @param variables 撤回者处理当前任务参数* @return*/Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);
@Overridepublic 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);
@Overridepublic 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等。
SpringBoot + Activiti6简单教程包含任意跳转节点相关推荐
- 最新Kafka教程(包含kafka部署与基本操作、java连接kafka、spring连接kafka以及使用springboot)
最新Kafka教程(包含kafka部署与基本操作.java连接kafka.spring连接kafka以及使用springboot) 欢迎转载,转载请注明网址:https://blog.csdn.net ...
- 很详细的SpringBoot整合UEditor教程
很详细的SpringBoot整合UEditor教程 2017年04月10日 20:27:21 小宝2333 阅读数:21529 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...
- springboot Activiti6
springboot Activiti6 流程建模 需要下载设计工具Activiti6.0 UI 运行设计工具 登录页 流程图效果 BPMN2.0构件 起点 结束 活动 流程线 网关 变量设置 画不同 ...
- expect简单教程
expect简单教程 一.概述 expect是Unix系统中用来进行自动化控制和测试的软件工具,由Don Libes制作,作为Tcl脚本语言的一个扩展,应用在交互式软件中如telnet,ftp,Pas ...
- ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单 ...
- ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单 ...
- ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core Identity 迁移数据 - ASP.NET C ...
- springboot 系列技术教程目录
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 一.教程目录地址: springboot系列技术教程目录 二.教程内容: springboot2.X ...
- ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core 项目配置 ( Startup ) - ASP ...
最新文章
- 2673(2673)shǎ崽 OrOrOrOrz
- eclipse安装ADT插件重启后不显示Android SDK Manager和Android Virtual Device Manager图标的一种解决办法
- 内核request_mem_region 和 ioremap的理解
- Java基础day24
- 时钟源为什么会影响性能
- nginx利用image_filter动态生成缩略图
- 奇怪的 Markdown / LaTeX 笔记
- android 布局可大可小,UI设计教程之:ios与android ui适配(将IOS UI转换成Android经验畅谈)...
- 全局系统性地把握客户感知-建立VOC
- python数字右对齐_python用format把float、int等数字字符串化设置左对齐右对齐居中对齐,宽度,保留几位...
- 软件测试高频面试题真实分享/网上银行转账是怎么测的,设计一下测试用例。
- matlab进化树的下载,mega(进化树构建软件)下载 v7.0.14免费版
- c计算机软考中级考什么,计算机软考中级考试内容
- 如何通过DOI号来获取相关文献并下载?
- 手把手教你 Charles 的使用「部分逆向的解决方法」
- 跳妹儿学编程之ScratchJr(三):什么是儿童编程语言?初识ScratchJr和Scratch
- shopex php5.3,shopex.4.85支持php5.3 | 学步园
- html5+一屏一区域内容,iPhoneX页面安全区域与内容重叠问题
- Zotero+Web of Science 实现批量导入下载文献
- 谈谈面试题之为什么用线程池?解释下线程池参数?