# activiti-service **Repository Path**: iotplc/activiti-service ## Basic Information - **Project Name**: activiti-service - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-13 - **Last Updated**: 2021-07-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README * [Activiti7.X结合SpringBoot2.1、Mybatis](#activiti7x结合springboot21mybatis) * [Activiti简介](#activiti简介) * [Activiti介绍](#activiti介绍) * [BPMN](#bpmn) * [创建应用](#创建应用) * [与mysql结合](#与mysql结合) * [数据库说明](#数据库说明) * [表详解](#表详解) * [工作流使用](#工作流使用) * [相关配置类](#相关配置类) * [自定义id策略](#自定义id策略) * [自定义用户组](#自定义用户组) * [其他自定义配置](#其他自定义配置) * [流程绘制](#流程绘制) * [前端实现自定义流程、输出流程图](#前端实现自定义流程输出流程图) * [接口调试](#接口调试) * [实现随意跳转和回退撤回功能](#实现随意跳转和回退撤回功能) # Activiti7.X结合SpringBoot2.1、Mybatis ## Activiti简介 ### Activiti介绍 ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/2317879.png) Activiti 是由 jBPM 的创建者 Tom Baeyens 离开 JBoss 之后建立的项目,构建在开发 jBPM 版本 1 到 4 时积累的多年经验的基础之上,旨在创建下一代的 BPM 解决方案。 Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。 Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于Java的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。 Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。 Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快速、稳定的BPMN e 2.0流程引擎。Activiti是在ApacheV2许可下发布的,可以运行在任何类型的Java程序中,例如服务器、集群、云服务等。Activiti可以完美地与Spring集成。同时,基于简约思想的设计使Activiti非常轻量级。 目前Activiti有2个版本,一个本地的core,一个可以支持分布式的cloud,本文只介绍core,新版 Activiti 7.0.0 发布后,Activiti Cloud 现在是新一代商业自动化平台,提供一组旨在在分布式基础架构上运行的 Cloud原生构建块。Cloud可以参考[官网](https://activiti.gitbook.io/activiti-7-developers-guide/getting-started) Activiti 7.x 主要突出了 Spring Boot 2.x 应用程序中的 ProcessRuntime 和 TaskRuntime API 的使用。 ### BPMN BPMN(Business Process Model And Notation)-业务流程模型和符号是由BPMI(Business Process Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。 Activiti 就是使用BPMN 2.0 进行流程建模、流程执行管理,它包括很多的建模符号,比如:Event 用一个圆圈表示,它是流程中运行过程中发生的事情。 一个bpmn图形的例子: 首先当事人发起一个请假单 其次他所在部门的经理对请假单进行审核 然后人事经理进行复核并进行备案 最后请假流程结束 ## 创建应用 代码库地址:https://github.com/dinghuang/activiti-service.git 创建maven应用,pom引入包 ``` 4.0.0 dinghuang-framework-parent org.dinghuang 0.1.0-RELEASE activiti 0.0.1-SNAPSHOT jar activiti activiti project for Spring Boot 1.8 UTF-8 UTF-8 1.3.0.Final 7.1.0.M6 2.1.0.RELEASE 1.2.47 3.2.2 3.8.1 1.5.16 2.7.0 1.1.10 3.1.0 9.5.0 1.16.20 8.0.15 3.5.3 2.0.1.Final 6.0.15.Final org.activiti.dependencies activiti-dependencies ${activiti.version} import pom org.springframework.boot spring-boot-starter-test test org.springframework spring-core 5.1.12.RELEASE org.activiti activiti-spring-boot-starter ${activiti.version} org.postgresql postgresql 42.2.10 org.activiti activiti-image-generator org.springframework.boot spring-boot-starter-thymeleaf ${spring-boot} org.dinghuang dinghuang-framework-core 0.1.0-RELEASE org.apache.maven.plugins maven-compiler-plugin 3.6.2 1.8 1.8 org.mapstruct mapstruct-processor ${mapstruct} org.projectlombok lombok 1.16.20 -Amapstruct.defaultComponentModel=spring org.springframework.boot spring-boot-maven-plugin demo ``` ### 与mysql结合 在``resource``目录下准备文件``activiti.cfg.xml``,内容如下: ``` ``` 执行下面的代码,初始化db数据库或者配置springboot配置文件,会自动生成25个表,代码执行如下: ``` package org.dinghuang.activiti.conf; import org.activiti.engine.impl.db.DbSchemaCreate; /** * @author dinghuang123@gmail.com * @since 2020/2/28 */ public class Test { public static void main(String[] args) { DbSchemaCreate.main(args); } } ``` 配置文件如下: ``` spring: activiti: # 自动建表 database-schema: ACTIVITI #表示启动时检查数据库表,不存在则创建 database-schema-update: true #表示哪种情况下使用历史表,这里配置为full表示全部记录历史,方便绘制流程图 history-level: full #表示使用历史表,如果不配置,则工程启动后可以检查数据库,只建立了17张表,历史表没有建立,则流程图及运行节点无法展示 db-history-used: true project: manifest: file: path: classpath:/default-project.json logging: level: org.activiti: debug ``` #### 数据库说明 数据库会生成25张表,ER图如图所示: ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/activiti.png) 表的列表: ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/WechatIMG430.png) ``` ACT_RE_*: RE表示repository,这个前缀的表包含了流程定义和流程静态资源 ACT_RU_*: RU表示runtime,这些运行时的表,包含流程实例,任务,变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 ACT_ID_*: ID表示identity,这些表包含身份信息,比如用户,组等。这些表现在已废弃。 ACT_HI_*: HI表示history,这些表包含历史数据,比如历史流程实例, 变量,任务等。 ACT_GE_*: 通用数据, 用于不同场景下。 ACT_EVT_*: EVT表示EVENT,目前只有一张表ACT_EVT_LOG,存储事件处理日志,方便管理员跟踪处理 ``` 具体表详解可以参考[文章](https://www.chenmin.info/2018/07/28/Activiti-23%E5%BC%A0%E8%A1%A8%E5%8F%8A7%E5%A4%A7%E6%9C%8D%E5%8A%A1%E8%AF%A6%E8%A7%A3/#7%E5%A4%A7%E6%9C%8D%E5%8A%A1%E4%BB%8B%E7%BB%8D) ### 表详解 表 | 意义 | 备注 ---|---|--- ACT_EVT_LOG| 事件处理日志| ACT_GE_BYTEARRAY| 二进制数据表 | 存储流程定义相关的部署信息。即流程定义文档的存放地。每部署一次就会增加两条记录,一条是关于bpmn规则文件的,一条是图片的(如果部署时只指定了bpmn一个文件,activiti会在部署时解析bpmn文件内容自动生成流程图)。两个文件不是很大,都是以二进制形式存储在数据库中。 ACT_GE_PROPERTY| 主键生成表|主张表将生成下次流程部署的主键ID。 ACT_HI_ACTINST| 历史节点表| 只记录usertask内容,某一次流程的执行一共经历了多少个活动 ACT_HI_ATTACHMENT| 历史附件表| ACT_HI_COMMENT| 历史意见表| ACT_HI_DETAIL| 历史详情表,提供历史变量的查询| 流程中产生的变量详细,包括控制流程流转的变量等 ACT_HI_IDENTITYLINK| 历史流程人员表| ACT_HI_PROCINST| 历史流程实例表 | ACT_HI_TASKINST| 历史任务实例表 |一次流程的执行一共经历了多少个任务 ACT_HI_VARINST| 历史变量表 | ACT_PROCDEF_INFO| | ACT_RE_DEPLOYMENT| 部署信息表 | 存放流程定义的显示名和部署时间,每部署一次增加一条记录 ACT_RE_MODEL| 流程设计模型部署表| 流程设计器设计流程后,保存数据到该表 ACT_RE_PROCDEF| 流程定义数据表 | 存放流程定义的属性信息,部署每个新的流程定义都会在这张表中增加一条记录。注意:当流程定义的key相同的情况下,使用的是版本升级 ACT_RU_EVENT_SUBSCR| throwEvent,catchEvent时间监听信息表| ACT_RU_EXECUTION| 运行时流程执行实例表| 历史流程变量 ACT_RU_IDENTITYLINK|运行时流程人员表|主要存储任务节点与参与者的相关信息 ACT_RU_INTEGRATION| | ACT_RU_JOB |运行时定时任务数据表 ACT_RU_TIMER_JOB| | ACT_RU_SUSPENDED_JOB| | ACT_RU_TASK |运行时任务节点表 | ACT_RU_TIMER_JOB| | ACT_RU_VARIABLE | 运行时流程变量数据表| 通过JavaBean设置的流程变量,在act_ru_variable中存储的类型为serializable,变量真正存储的地方在act_ge_bytearray中。 ACT_ID_GROUP| 用户组信息表| 已废弃 ACT_ID_INFO| 用户扩展信息表 |已废弃 ACT_ID_MEMBERSHIP| 用户与用户组对应信息表|已废弃 ACT_ID_USER |用户信息表 |已废弃 ## 工作流使用 activiti7内置了Spring security框架,官方demo跟spring结合的必须与spring-security结合,这里我不用spring-security,因为现在没有用户表了,所以自定义一些用户角色表去结合,更容易理解。 > 关于security问题 activiti7最新的类似Runtime API和Task API都集成了security。 如果使用上述的API,那么必须要使用security,不能屏蔽security,否则会报错。 使用引擎服务类的时候,可以排除security,因为这些是最原始的API。但是activiti7官方已经明确说了,随时可能会干掉这些API。不建议开发人员直接使用引擎类以及引擎配置了、服务类等。 ### 相关配置类 #### 自定义id策略 ``` package org.dinghuang.activiti.conf; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.activiti.engine.impl.cfg.IdGenerator; import org.springframework.stereotype.Component; /** * 自定义id策略 * * @author dinghuang123@gmail.com * @since 2020/3/4 */ @Component public class ActivitiIdGeneratorConfiguration implements IdGenerator { @Override public String getNextId() { return String.valueOf(IdWorker.getId()); } } ``` #### 自定义用户组 activiti7已经抛弃了identity相关的表,同时与springSecurity结合,所以这边如果想自定义可以这么改,但是实际用途不大,最重要的是要结合自己的用户表设计来。 ``` package org.dinghuang.activiti.conf; import org.activiti.api.runtime.shared.identity.UserGroupManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 重写用户权限 * * @author dinghuang123@gmail.com * @since 2020/3/2 */ @Component public class ActivitiUserGroupManagerConfiguration implements UserGroupManager { private static final Logger LOGGER = LoggerFactory.getLogger(ActivitiUserGroupManagerConfiguration.class); public static List roles = new ArrayList<>(3); public static List groups = new ArrayList<>(1); public static List users = new ArrayList<>(3); public static Map userRoleMap = new HashMap<>(3); static { roles.add("workCreate"); roles.add("workPermit"); roles.add("workLeader"); groups.add("workGroupA"); users.add("admin"); users.add("laowang"); users.add("xiaofang"); userRoleMap.put("admin", "workCreate"); userRoleMap.put("laowang", "workPermit"); userRoleMap.put("xiaofang", "workLeader"); } @Override public List getUserGroups(String s) { LOGGER.info("get user groups"); return groups; } @Override public List getUserRoles(String s) { String role = userRoleMap.get(s); List list = new ArrayList<>(); list.add(role); LOGGER.info("get user roles"); return list; } @Override public List getGroups() { LOGGER.info("get groups"); return groups; } @Override public List getUsers() { LOGGER.info("get users"); return users; } } ``` #### 其他自定义配置 ``` package org.dinghuang.activiti.conf; import org.activiti.api.runtime.shared.identity.UserGroupManager; import org.activiti.core.common.spring.project.ProjectModelService; import org.activiti.engine.impl.history.HistoryLevel; import org.activiti.spring.SpringAsyncExecutor; import org.activiti.spring.SpringProcessEngineConfiguration; import org.dinghuang.activiti.controller.ActivitiController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.io.IOException; /** * @author dinghuang123@gmail.com * @since 2020/3/2 */ @Configuration public class ActivitiSpringIdentityAutoConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(ActivitiController.class); private static final int CORE_POOL_SIZE = 10; private static final int MAX_POOL_SIZE = 30; private static final int KEEP_ALIVE_SECONDS = 300; private static final int QUEUE_CAPACITY = 300; @Autowired private DataSource dataSource; @Autowired private PlatformTransactionManager platformTransactionManager; @Autowired private UserGroupManager userGroupManager; @Autowired private ActivitiIdGeneratorConfiguration activitiIdGeneratorConfiguration; @Autowired private ProjectModelService projectModelService; /** * 处理引擎配置 */ @Bean public SpringProcessEngineConfiguration springProcessEngineConfiguration() { SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration(projectModelService); configuration.setDataSource(this.dataSource); configuration.setTransactionManager(this.platformTransactionManager); SpringAsyncExecutor asyncExecutor = new SpringAsyncExecutor(); asyncExecutor.setTaskExecutor(workFlowAsync()); configuration.setAsyncExecutor(asyncExecutor); configuration.setDatabaseSchemaUpdate("true"); configuration.setUserGroupManager(this.userGroupManager); configuration.setHistoryLevel(HistoryLevel.FULL); configuration.setDbHistoryUsed(true); configuration.setIdGenerator(this.activitiIdGeneratorConfiguration); Resource[] resources = null; // 启动自动部署流程 try { resources = new PathMatchingResourcePatternResolver().getResources("classpath*:bpmn/*.bpmn"); } catch (IOException e) { LOGGER.error("Start the automated deployment process error", e); } configuration.setDeploymentResources(resources); return configuration; } /** * 线程池 */ @Primary @Bean("workFlowTaskExecutor") public ThreadPoolTaskExecutor workFlowAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(CORE_POOL_SIZE); executor.setMaxPoolSize(MAX_POOL_SIZE); executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS); executor.setQueueCapacity(QUEUE_CAPACITY); executor.setThreadNamePrefix("workFlowTaskExecutor-"); executor.initialize(); return executor; } } ``` 因为去掉了springSecurity,所以启动类得排除springSecurity的自动配置。 ``` package org.dinghuang.activiti; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; @SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration.class, org.activiti.core.common.spring.identity.config.ActivitiSpringIdentityAutoConfiguration.class }) @EnableWebMvc public class ActivitiApplication { public static void main(String[] args) { SpringApplication.run(ActivitiApplication.class, args); } @Bean public InternalResourceViewResolver setupViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); /** 设置视图路径的前缀 */ resolver.setPrefix("resources/templates"); /** 设置视图路径的后缀 */ resolver.setSuffix(".html"); return resolver; } } ``` ## 流程绘制 流程绘制这里提供2种,一种用IDEA下载ActiBPM插件去画,这里给一段我自己画的xml ``` ``` 这是一个很简单的流程,如图所示: ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/WechatIMG432.png) ### 前端实现自定义流程、输出流程图 官方提供bpmn.js可以实现前端拖动画图,github地址:https://github.com/bpmn-io/bpmn-js 可以把这个加到项目中,这里就不解释了。 前端实现输出流程图效果如下,流程节点会高亮显示: ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/WechatIMG433.png) 这里是我写的一个小demo,代码可以查看我的github代码库。项目启动后,访问http://localhost:8080/v1/activiti/index ## 接口调试 服务启动后访问地址:http://localhost:8080/swagger-ui.html#/ 接口已经写在代码中,如果需要调试的话,顺序是 - 启动实例流程 - 根据用户名称查询任务列表(用户名bilu,拿到任务id后去完成任务) - 审批approve - 回退 back ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/WechatIMG434.png) ### 实现随意跳转和回退撤回功能 因为activiti7是以图的形式来操作的,所以这边就要考虑连线的情况。 本文写了并行和串行的撤回以及撤回功能的连线的图,最终效果图如图所示 ![image](https://minios.strongsickcat.com/dinghuang-blog-picture/WechatIMG451.png) ``` /** * 输出图像 * * @param response 响应实体 * @param bpmnModel 图像对象 * @param flowIds 已执行的线集合 * @param executedActivityIdList void 已执行的节点ID集合 */ public void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List flowIds, List executedActivityIdList) { InputStream imageStream = null; try { imageStream = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png"); // 输出资源内容到相应对象 byte[] b = new byte[1024]; int len; while ((len = imageStream.read(b, 0, 1024)) != -1) { response.getOutputStream().write(b, 0, len); } response.getOutputStream().flush(); } catch (Exception e) { LOGGER.error("out put process img error!", e); } finally { // 流关闭 try { if (imageStream != null) { imageStream.close(); } } catch (IOException e) { LOGGER.error("IoException", e); } } } public void showImg(String instanceKey, HttpServletResponse response) { if (instanceKey == null) { LOGGER.error("process instance not exist"); return; } //获取流程实例 HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceKey).singleResult(); if (processInstance == null) { LOGGER.error("process instance {} not exist", instanceKey); return; } // 根据流程对象获取流程对象模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); //查看已执行的节点集合, 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序 // 构造历史流程查询 HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceKey); // 查询历史节点,根据id排序 List historicActivityInstanceList = historyInstanceQuery.orderBy(HistoricActivityInstanceQueryProperty.HISTORIC_ACTIVITY_INSTANCE_ID).asc().list(); if (historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) { LOGGER.info("process instance history node info not exist", instanceKey); outputImg(response, bpmnModel, null, null); return; } Map flowNodeMap = getFlowNodeMap(historicActivityInstanceList, processInstance.getProcessDefinitionId()); //处理撤回这种情况 根据id排序 List tasks = taskService.createTaskQuery().processInstanceId(instanceKey).orderBy(TaskQueryProperty.TASK_ID).asc().list(); Set executedActivityIdList = new LinkedHashSet<>(); List taskIdList = tasks == null ? null : tasks.stream().map(TaskInfo::getId).collect(Collectors.toList()); List taskKeyList = tasks == null ? null : tasks.stream().map(TaskInfo::getTaskDefinitionKey).collect(Collectors.toList()); if (tasks != null) { //串行 if (tasks.size() == 1) { for (int i = 0; i < historicActivityInstanceList.size(); i++) { if (historicActivityInstanceList.get(i).getTaskId() == null || !historicActivityInstanceList.get(i).getActivityId().equals(tasks.get(0).getTaskDefinitionKey())) { executedActivityIdList.add(historicActivityInstanceList.get(i).getActivityId()); } else { executedActivityIdList.add(historicActivityInstanceList.get(i).getActivityId()); break; } } } else { List historicVariableInstances = historyService.createHistoricVariableInstanceQuery() .processInstanceId(processInstance.getId()).list(); Map historicVariableInstanceMap = historicVariableInstances.stream() .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, historicVariableInstance -> historicVariableInstance, BinaryOperator.maxBy(Comparator.comparing(HistoricVariableInstance::getId)))); //并行 Collection flowElementCollection = bpmnModel.getMainProcess().getFlowElements(); Map> parentMap = new HashMap<>(tasks.size()); for (FlowElement flowElement : flowElementCollection) { List parentCodeList = new LinkedList<>(); if (flowNodeMap.get(flowElement.getId()) != null) { List sequenceFlows = flowNodeMap.get(flowElement.getId()).getIncomingFlows(); if (sequenceFlows != null && !sequenceFlows.isEmpty()) { for (SequenceFlow sequenceFlow : sequenceFlows) { parentCodeList.add(sequenceFlow.getSourceRef()); } parentMap.put(flowElement.getId(), parentCodeList); } } } Set sameParentTaskCode = new LinkedHashSet<>(); for (Task task : tasks) { //找到所有任务拥有相同父级的集合任务 for (String taskKey : parentMap.get(task.getTaskDefinitionKey())) { for (String key : parentMap.keySet()) { if (parentMap.get(key).contains(taskKey)) { sameParentTaskCode.add(key); break; } } } } //说明是并行,但是做完的任务 for (String sameParentTask : sameParentTaskCode) { if (!taskKeyList.contains(sameParentTask)) { List sequenceFlows = flowNodeMap.get(sameParentTask).getOutgoingFlows(); if (sequenceFlows != null && !sequenceFlows.isEmpty()) { for (SequenceFlow sequenceFlow : sequenceFlows) { if (querySequenceFlowCondition(sequenceFlow, historicVariableInstanceMap)) { executedActivityIdList.add(sequenceFlow.getTargetRef()); } } } } } for (String taskKey : sameParentTaskCode) { for (int i = 0; i < historicActivityInstanceList.size(); i++) { if (historicActivityInstanceList.get(i).getTaskId() == null || !historicActivityInstanceList.get(i).getActivityId().equals(taskKey)) { executedActivityIdList.add(historicActivityInstanceList.get(i).getActivityId()); } else { executedActivityIdList.add(historicActivityInstanceList.get(i).getActivityId()); break; } } } } } //获取流程走过的线 List executedActivityIdListResult = new ArrayList<>(executedActivityIdList); List flowIds = getHighLightedFlows(bpmnModel, historicActivityInstanceList, executedActivityIdListResult, taskIdList); //输出图像,并设置高亮 outputImg(response, bpmnModel, flowIds, executedActivityIdListResult); } private Map getFlowNodeMap(List historicActivityInstanceList, String processDefinitionId) { org.activiti.bpmn.model.Process process = repositoryService .getBpmnModel(processDefinitionId) .getMainProcess(); Map flowNodeMap = new HashMap<>(historicActivityInstanceList.size()); for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) { if (flowNodeMap.get(historicActivityInstance.getActivityId()) == null) { FlowNode sourceNode = (FlowNode) process.getFlowElement(historicActivityInstance.getActivityId()); flowNodeMap.put(historicActivityInstance.getActivityId(), sourceNode); } } return flowNodeMap; } /** * 撤回任务 * * @param currentTaskId currentTaskId * @param targetTaskId targetTaskId 目标任务,如果为空,默认返回上级,如果找到上级有2个,那目标任务必须得传 */ @Transactional(rollbackFor = Exception.class) public void backTask(String currentTaskId, String targetTaskId) { //准备数据 TaskService taskService = processEngine.getTaskService(); // 当前任务 Task currentTask = taskService.createTaskQuery().taskId(currentTaskId).singleResult(); String processInstanceId = currentTask.getProcessInstanceId(); // 获取流程定义 //任务历史数据 List historicTaskInstances = historyService .createHistoricTaskInstanceQuery() .processInstanceId(currentTask.getProcessInstanceId()) .orderBy(HistoricTaskInstanceQueryProperty.HISTORIC_TASK_INSTANCE_ID) .desc() .list(); Map historicTaskInstanceMap = historicTaskInstances.stream().collect(Collectors.toMap(HistoricTaskInstance::getId, Function.identity())); //所有节点操作数据 HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId); List historicActivityInstanceList = historyInstanceQuery.orderBy(HistoricActivityInstanceQueryProperty.HISTORIC_ACTIVITY_INSTANCE_ID).asc().list(); Map> historicActivityInstanceMap = historicActivityInstanceList.stream().collect(Collectors.groupingBy(HistoricActivityInstance::getActivityId)); Map flowNodeMap = getFlowNodeMap(historicActivityInstanceList, currentTask.getProcessDefinitionId()); //排除当前任务外的所有正在进行的任务 List taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list().stream().filter(task -> !task.getId().equals(currentTask.getId())).collect(Collectors.toList()); handleBackTask(currentTask, currentTask.getTaskDefinitionKey(), targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } @Transactional(rollbackFor = Exception.class) public void handleBackTask(Task currentTask, String taskDefinitionKey, String targetTaskId, Map historicTaskInstanceMap, Map> historicActivityInstanceMap, Map flowNodeMap, List taskList, List historicActivityInstanceList) { //判断是否并行 if (taskList == null || taskList.isEmpty()) { //串行 handleSerial(currentTask, taskDefinitionKey, targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } else { //并行 handleParallel(currentTask, taskDefinitionKey, targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } } @Transactional(rollbackFor = Exception.class) public void handleParallel(Task currentTask, String taskDefinitionKey, String targetTaskId, Map historicTaskInstanceMap, Map> historicActivityInstanceMap, Map flowNodeMap, List taskList, List historicActivityInstanceList) { List sequenceFlows = flowNodeMap.get(taskDefinitionKey).getIncomingFlows(); if (sequenceFlows.size() == 1) { //当前节点的上级节点只有一条 SequenceFlow sequenceFlow = sequenceFlows.get(0); //查询历史节点 HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.stream().filter(query -> query.getActivityId().equals(sequenceFlow.getSourceRef())).collect(Collectors.toList()).get(0); //判断来源类型 if (historicActivityInstance.getActivityType().equals(PARALLEL_GATEWAY)) { //网关 //查找网关的父任务 Set parentFlowNodes = queryParentFlowNode(historicActivityInstance.getActivityId(), flowNodeMap); if (!parentFlowNodes.isEmpty()) { if (parentFlowNodes.size() == 1) { //如果只有一个父节点 String activityId = new ArrayList<>(parentFlowNodes).get(0); if (historicActivityInstanceMap.get(activityId).get(0).getActivityType().equals(USER_TASK)) { //用户任务 deleteTaskMultiple(flowNodeMap, null, null, activityId, currentTask, taskList, historicActivityInstance.getActivityId()); } else { //递归去查找父任务的前一个 handleBackTask(currentTask, historicActivityInstanceMap.get(activityId).get(0).getActivityId(), targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } } else { //当前节点的上级节点有多条 这里需要指定要回退的taskId deleteTaskMultiple(flowNodeMap, historicTaskInstanceMap, targetTaskId, null, currentTask, taskList, historicActivityInstance.getActivityId()); } } else { //没有父级任务,图有问题 throw new CommonValidateException("bpmn doc error"); } } else if (historicActivityInstance.getActivityType().equals(USER_TASK)) { //用户任务 deleteTaskMultiple(flowNodeMap, null, null, historicActivityInstance.getActivityId(), currentTask, taskList, historicActivityInstance.getActivityId()); } else { //todo 还没想好这种场景 throw new CommonValidateException(BPMN_NOT_SUPPORT); } } else { //当前节点的上级节点有多条 这里需要指定要回退的taskId deleteTaskMultiple(flowNodeMap, historicTaskInstanceMap, targetTaskId, null, currentTask, taskList, null); } } @Transactional(rollbackFor = Exception.class) public void handleSerial(Task currentTask, String taskDefinitionKey, String targetTaskId, Map historicTaskInstanceMap, Map> historicActivityInstanceMap, Map flowNodeMap, List taskList, List historicActivityInstanceList) { FlowNode currentNode = flowNodeMap.get(taskDefinitionKey); List sequenceFlows = currentNode.getIncomingFlows(); if (sequenceFlows.size() == 1) { SequenceFlow sequenceFlow = sequenceFlows.get(0); HistoricActivityInstance historicActivityInstance = historicActivityInstanceMap.get(sequenceFlow.getSourceRef()).get(0); //网关 if (historicActivityInstance.getActivityType().equals(PARALLEL_GATEWAY) || historicActivityInstance.getActivityType().equals(EXCLUSIVE_GATEWAY)) { //查找网关的父任务 Set parentFlowNodes = queryParentFlowNode(historicActivityInstance.getActivityId(), flowNodeMap); if (!parentFlowNodes.isEmpty()) { handleBackTaskSingle(parentFlowNodes, currentTask, targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } else { //当前节点的上级节点有多条 这里需要指定要回退的taskId deleteTaskMultiple(flowNodeMap, historicTaskInstanceMap, targetTaskId, null, currentTask, taskList, null); } } else if (historicActivityInstance.getActivityType().equals(USER_TASK)) { deleteTaskSingle(flowNodeMap, historicActivityInstance.getActivityId(), currentTask.getId()); } else { //todo 还没想好这种场景 throw new CommonValidateException(BPMN_NOT_SUPPORT); } } else { Map historicVariableInstanceMap = getHistoricVariableInstanceMap(currentTask.getProcessInstanceId()); //串行的也有多条连线,可能是通过排他网关过来的 Set historicActivityInstances = new HashSet<>(); for (SequenceFlow sequenceFlow : sequenceFlows) { //这边他的parent可能是没做过的,要找做过的 if (historicActivityInstanceMap.get(sequenceFlow.getSourceRef()) != null && querySequenceFlowCondition(sequenceFlow, historicVariableInstanceMap)) { historicActivityInstances.addAll(historicActivityInstanceMap.get(sequenceFlow.getSourceRef())); } } //走过的只有一个 if (historicActivityInstances.size() == 1) { List historicActivityInstancesList = new ArrayList<>(historicActivityInstances); if (historicActivityInstancesList.get(0).getActivityType().equals(USER_TASK)) { deleteTaskSingle(flowNodeMap, historicActivityInstancesList.get(0).getActivityId(), currentTask.getId()); } else if (historicActivityInstancesList.get(0).getActivityType().equals(EXCLUSIVE_GATEWAY)) { //排他网关 Set parentFlowNodes = queryParentFlowNode(historicActivityInstancesList.get(0).getActivityId(), flowNodeMap); handleBackTaskSingle(parentFlowNodes, currentTask, targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } else { //todo 还没想好这种场景 throw new CommonValidateException(BPMN_NOT_SUPPORT); } } else { //当前节点的上级节点有多条 这里需要指定要回退的taskId deleteTaskMultiple(flowNodeMap, historicTaskInstanceMap, targetTaskId, null, currentTask, taskList, null); } } } private Map getHistoricVariableInstanceMap(String processInstanceId) { List historicVariableInstances = historyService.createHistoricVariableInstanceQuery() .processInstanceId(processInstanceId).list(); Map historicVariableInstanceMap = historicVariableInstances.stream() .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, historicVariableInstance -> historicVariableInstance, BinaryOperator.maxBy(Comparator.comparing(HistoricVariableInstance::getId)))); return historicVariableInstanceMap; } @Transactional(rollbackFor = Exception.class) public void handleBackTaskSingle(Set parentFlowNodes, Task currentTask, String targetTaskId, Map historicTaskInstanceMap, Map> historicActivityInstanceMap, Map flowNodeMap, List taskList, List historicActivityInstanceList) { if (parentFlowNodes.size() == 1) { List parentFlowNodeList = new ArrayList<>(parentFlowNodes); if (historicActivityInstanceMap.get(parentFlowNodeList.get(0)).get(0).getActivityType().equals(USER_TASK)) { deleteTaskSingle(flowNodeMap, parentFlowNodeList.get(0), currentTask.getId()); } else { //递归去查找父任务的前一个 handleBackTask(currentTask, historicActivityInstanceMap.get(parentFlowNodeList.get(0)).get(0).getActivityId(), targetTaskId, historicTaskInstanceMap, historicActivityInstanceMap, flowNodeMap, taskList, historicActivityInstanceList); } } else { //当前节点的上级节点有多条 这里需要指定要回退的taskId deleteTaskMultiple(flowNodeMap, historicTaskInstanceMap, targetTaskId, null, currentTask, taskList, null); } } private void validatorTargetTask(Map historicTaskInstanceMap, String targetTaskId) { if (StringUtils.isEmpty(targetTaskId) || StringUtils.isBlank(targetTaskId)) { throw new CommonValidateException("target task id cannot be null"); } if (historicTaskInstanceMap == null || historicTaskInstanceMap.isEmpty()) { throw new CommonValidateException("historic task instance cannot be null"); } } @Transactional(rollbackFor = Exception.class) public void deleteTaskMultiple(Map flowNodeMap, Map historicTaskInstanceMap, String targetTaskId, String targetTaskDefinitionKey, Task currentTask, List taskList, String targetParentTaskDefinitionKey) { if (StringUtils.isEmpty(targetTaskDefinitionKey) || StringUtils.isBlank(targetTaskDefinitionKey)) { validatorTargetTask(historicTaskInstanceMap, targetTaskId); targetTaskDefinitionKey = historicTaskInstanceMap.get(targetTaskId).getTaskDefinitionKey(); } FlowNode targetNode = flowNodeMap.get(targetTaskDefinitionKey); ManagementService managementService = processEngine.getManagementService(); //删除当前任务 managementService.executeCommand(new DeleteTaskCmd(currentTask.getId())); // 删除当前运行的其他相同父任务的子任务 Set sameParentTasks = getSameParentTasks(flowNodeMap, taskList, targetParentTaskDefinitionKey); for (Task task : sameParentTasks) { managementService.executeCommand(new DeleteTaskCmd(task.getId())); } // 流程执行到来源节点 managementService.executeCommand(new SetFLowNodeAndGoCmd(targetNode, currentTask.getExecutionId())); } @Transactional(rollbackFor = Exception.class) public void deleteTaskSingle(Map flowNodeMap, String targetTaskActivitiId, String currentTaskId) { ManagementService managementService = processEngine.getManagementService(); FlowNode targetNode = flowNodeMap.get(targetTaskActivitiId); // 删除当前运行任务 String executionEntityId = managementService.executeCommand(new DeleteTaskCmd(currentTaskId)); // 流程执行到来源节点 managementService.executeCommand(new SetFLowNodeAndGoCmd(targetNode, executionEntityId)); } private Set queryParentFlowNode(String activityId, Map flowNodeMap) { Set flowNodeList = new HashSet<>(); for (String key : flowNodeMap.keySet()) { if (!key.equals(activityId)) { FlowNode flowNode = flowNodeMap.get(key); List sequenceFlows = flowNode.getOutgoingFlows(); for (SequenceFlow sequenceFlow : sequenceFlows) { if (sequenceFlow.getTargetRef().equals(activityId)) { flowNodeList.add(key); break; } } } } return flowNodeList; } private Set getSameParentTasks(Map flowNodeMap, List taskList, String taskDefinitionKey) { if (taskDefinitionKey == null) { return new HashSet<>(taskList); } Set tasks = new HashSet<>(); for (Task task : taskList) { List sequenceFlows = flowNodeMap.get(task.getTaskDefinitionKey()).getIncomingFlows(); for (SequenceFlow sequenceFlow : sequenceFlows) { if (sequenceFlow.getSourceRef().equals(taskDefinitionKey)) { tasks.add(task); break; } } } return tasks; } @Transactional(rollbackFor = Exception.class) public void importBpmnFile(MultipartFile file, String type, String typeName) { try { InputStream fileInputStream = file.getInputStream(); //创建转换对象 BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter(); //读取xml文件 XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(fileInputStream); //将xml文件转换成BpmnModel BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xmlStreamReader); bpmnModel.getMainProcess().setId(type); bpmnModel.getMainProcess().setName(typeName); Deployment deployment = repositoryService.createDeployment() .addBpmnModel(typeName + ".bpmn", bpmnModel) .key(IdWorker.getIdStr()) .deploy(); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult(); BpmnModel model = repositoryService.getBpmnModel(processDefinition.getId()); if (model != null) { Collection flowElementCollection = model.getMainProcess().getFlowElements(); for (FlowElement e : flowElementCollection) { LOGGER.info("flowelement id:" + e.getId() + " name:" + e.getName() + " class:" + e.getClass().toString()); } } activitiRepository.updateActReProcdef(processDefinition.getId()); } catch (Exception e) { LOGGER.error("导入流程定义失败:{}", e.getMessage(), e); } } ```