前言

工作流程是我们日常开发项目中常见的功能,本文主要springboot整合activiti7,梳理activiti在工作中的一些常见用法和功能

一、工作流介绍

1.1 概述

工作流(Workflow):就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。

1.2 常见工作流

采用工作流有以下优点
1、提高系统的柔性,适应业务流程的变化

2、实现更好的业务过程控制,提高顾客服务质量

3、降低系统开发和维护成本

常见工作流:Activiti、JBPM、OSWorkflow、ActiveBPEL、YAWL等。本文主要采用activiti7工作流开发

1.3 Activiti7

Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动。Activiti是一个工作流引擎, activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。

官方地址:https://www.activiti.org/

Activiti6.0开发手册:https://www.activiti.org/userguide/#bpmnMultiInstance

GitHub Activiti7开发手册地址:https://github.com/Activiti/activiti-7-developers-guide

二、工作流术语

2.1 工作流引

ProcessEngine对象: 这是Activiti工作的核心.负责生成流程运行时的各种实例及数据,监控和管理流程的运行

2.2 BPM

BPM(业务流程管理):是一种以规范化的构造端到端的卓越业务流程为中心,以持续的提高组织业务绩效为目的的系统化方法

2.3 BPMN

BPMN(Business Process Model and Notation) 业务流程建模与标注,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)bpmn文件又可以叫做流程定义文件,它需要遵循BPMN语言规。

2.4 流对象

流对象(process engine)通过它可以获得我们需要的一切activiti服务

一个业务流程图有三个流对象的核心元素

  • 事件
    一个事件用圆圈来描述,表示一个业务流程期间发生的东西。事件影响流程的流动,一般有一个原因(触发器)或一个影响(结果),基于它们对流程的影响,有三种事件:开始事件,中间事件,终止事件。
  • 活动
    用圆角矩形表示,一个流程由一个活动或多个活动组成
  • 条件
    条件用菱形表示,用于控制序列流的分支与合并,可以作为选择,包括路径的分支与合,内部的标记会给出控制流的类型

三、Activiti结构

3.1 Activiti系统服务结构图

  • 核心类:ProcessEngine: 流程引擎的抽象,可以通过此类获取需要的所有服务
  • 服务类:XxxService: 通过ProcessEngine获取,Activiti将不同生命周期的服务封装在不同Service中,包括定义,部署,运行.通过服务类可获取相关生命周期中的服务信息
    • RepositoryService
      Repository Service提供了对repository的存取服,Activiti中每一个不同版本的业务流程的定义都需要使用一些定义文件,部署文件和支持数据(例如BPMN2.0XML文件,表单定义文件,流程定义图像文件等),这些文件都存储在Activiti内建的Repository中
    • RuntimeService:
      Runtime Service提供了启动流程,查询流程实例,设置获取流程实例变量等功能.此外它还提供了对流程部署,流程定义和流程实例的存取服务
    • TaskService:
      Task Service提供了对用户Task和Form相关的操作.它提供了运行时任务查询,领取,完成,删除以及变量设置等功能
    • HistoryService
      History Service用于获取正在运行或已经完成的流程实例的信息,与Runtime Service获取的流程信息不同,历史信息包含已经持久化存储的永久信息,并已经被针对查询优化
    • FormService
      使用Form Service可以存取启动和完成任务所需的表单数据并且根据需要来渲染表单
      Activiti中的流程和状态Task均可以关联业务相关的数据
    • IdentityService
      Identity Service提供了对Activiti系统中的用户和组的管理功,Activiti中内置了用户以及组管理的功能,必须使用这些用户和组的信息才能获取到相应的Task
    • ManagementService
      Management Service提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用。主要用于 Activiti 系统的日常维护核心业务对象:org.activiti.engine.impl.persistence.entity包下的类,Task,ProcessInstance,Execution等根据不同职责实现相应接口的方法(如需要持久化则继承PersistentObject接口),与传统的实体类不同
      (注:Activiti7删除了FormService和IdentityService接口),工作流程与业务解耦,结合具体业务自由的配置用户和用户组。

3.2 Activiti数据库表结构

Activiti7工作流总共包含25张数据表(Activiti6包含23张表),Activiti会自动帮你生成这25张表,所有的表名默认以“ACT_”开头。


Activiti 使用到的表都是 ACT_ 开头的。表名的第二部分用两个字母表明表的用途:

  • ACT_GE_ (GE) 表示 general 全局通用数据及设置,各种情况都使用的数据。
  • ACT_HI_ (HI) 表示 history 历史数据表,包含着程执行的历史相关数据,如结束的流程实例,变量,任务,等等
  • ACT_RE_ (RE) 表示 repository 存储,包含的是静态信息,如,流程定义,流程的资源(图片,规则等)。
  • ACT_RU_ (RU) 表示 runtime 运行时,运行时的流程变量,用户任务,变量,职责(job)等运行时的数据。Activiti 只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。

Activiti7 25张表含义

表名 介绍
act_evt_log 流程引擎通用日志表
act_ge_bytearray 二进制表,存储通用的流程资源
act_ge_property 系统存储表,存储整个流程引擎数据,默认存储三条数据
act_hi_actinst 历史节点表
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 流程定义数据表
act_ru_deadletter_job 作业失败表,失败次数>重试次数
act_ru_event_subscr 运行时事件表
act_ru_execution 运行时流程执行实例表
act_ru_identitylink 运行时用户信息表
act_ru_integration 运行时综合表
act_ru_job 作业表
act_ru_suspended_job 作业暂停表
act_ru_task 运行时任务信息表
act_ru_timer_job 运行时定时器表
act_ru_variable 运行时变量表

四、流程步骤

4.1 部署Activiti

Activiti是一个工作流引擎(其实就是一堆jar包API),业务系统访问(操作)activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起。

4.2 流程定义

工作流要有流程模型图,使用activiti流程建模工具(activity-designer)定义业务流程(.bpmn文件) 绘制好流程模型,.bpmn文件就是业务流程定义文件,通过xml定义业务流程。

4.3 流程定义部署

将画好的流程图(activiti部署业务流程定义(.bpmn文件)),使用activiti提供的api把流程定义内容存储起来,在Activiti执行过程中可以查询定义的内容,Activiti执行把流程定义内容存储在数据库中

4.4 启动一个流程实例

流程实例也叫:ProcessInstance,启动一个流程实例表示开始一次业务流程的运行。在启动流程实例之前可以配置相应的业务需求,将某个业务绑定到当前流程上

4.5 用户查询待办任务(Task)

将系统的业务流程已经交给activiti管理,通过activiti就可以查询当前流程执行到哪里了,当前用户需要办理什么任务,activiti帮我们管理执行操作

4.6 用户已办任务历史记录

用户可以查询已经办理的业务和正在处理的任务,查询历史任务表获得具体流程执行细节,当一个流程没有下一节点时,整个流程实例就执行结束。

五、springboot整合Activiti

5.1 springboot整合Activiti环境依赖

1)maven环境

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.mengxuegu</groupId><artifactId>activiti-boot</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version><relativePath/></parent><properties><activiti.version>7.1.0.M6</activiti.version><mybatis-plus.version>3.3.1</mybatis-plus.version></properties><dependencies><!-- Activiti --><dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter</artifactId><version>${activiti.version}</version></dependency><!-- java绘制activiti流程图 --><dependency><groupId>org.activiti</groupId><artifactId>activiti-image-generator</artifactId><version>${activiti.version}</version></dependency><!-- activiti json转换器--><dependency><groupId>org.activiti</groupId><artifactId>activiti-json-converter</artifactId><version>${activiti.version}</version></dependency><!-- svg转png图片工具--><dependency><groupId>org.apache.xmlgraphics</groupId><artifactId>batik-all</artifactId><version>1.10</version></dependency><!-- web启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--mybatis-plusǷ--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- SpringSecurity --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency><!-- log start --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>${slf4j.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2)添加log4j日志配置
我们使用log4j日志包,可以对日志进行配置,在resources 下创建log4j.properties

# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=f:\act\activiti.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n

3)添加activiti配置文件

  • 官方采用默认方式的要求是在 resources 下创建 activiti.cfg.xml 文件,注意:默认方式目录和文件名不能修改,因为activiti的源码中已经设置,到固定的目录读取固定文件名的文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

我们这里不做采用,直接配置application.yaml

server:port: 9889servlet:context-path: /com/actspring:mvc:static-path-pattern: /**cloud:nacos:discovery:server-addr: 填写自己地址datasource:druid:配置自己数据库连接# 初始化大小,最小,最大initial-size: 1min-idle: 3max-active: 20# 配置获取连接等待超时的时间max-wait: 6000# activiti配置activiti:#自动更新数据库结构# true:适用开发环境,默认值。activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建# false:适用生产环境。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常# create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)# drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)database-schema-update: false# activiti7与springboot整合后默认不创建历史表,需要手动开启db-history-used: true# 记录历史等级 可配置的历史级别有none, activity, audit, full# none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。# activity:级别高于none,保存流程实例与流程行为,其他数据不保存。# audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。# full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。history-level: full# 是否自动检查resources下的processes目录的流程定义文件check-process-definitions: false
#    # smtp服务器地址
#    mail-server-host:
#    # SSL端口号
#    mail-server-port:
#    # 开启ssl协议
#    mail-server-use-ssl: true
#    # 默认的邮件发送地址(发送人),如果activiti流程定义中没有指定发送人,则取这个值
#    mail-server-default-from:
#    # 邮件的用户名
#    mail-server-user-name:deployment-mode: never-faillogging:level:com:oneconnect:sg: debug# 日志级别是debug才能显示SQL日志org.activiti.engine.impl.persistence.entity: inf
mybatis-plus:type-aliases-package: com.act.entity# xxxMapper.xml 路径mapper-locations: classpath*:com/act/mapper/**/*.xml

5.2 流程操作—流程模型

1)绘制流程模型编辑器

我们这里用java绑定activity-designer模型编辑器绘制模型,导入官方编辑器文件

  • ModelEditorJsonRestResource
package com.oneconnect.sg.act.activiti;import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;/*** @author Tijs Rademakers*/
@RestController
public class ModelEditorJsonRestResource implements ModelDataJsonConstants {protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate ObjectMapper objectMapper;@RequestMapping(value="/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json")public ObjectNode getEditorJson(@PathVariable String modelId) {ObjectNode modelNode = null;Model model = repositoryService.getModel(modelId);if (model != null) {try {if (StringUtils.isNotEmpty(model.getMetaInfo())) {modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());} else {modelNode = objectMapper.createObjectNode();modelNode.put(MODEL_NAME, model.getName());}modelNode.put(MODEL_ID, model.getId());ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));modelNode.put("model", editorJsonNode);} catch (Exception e) {LOGGER.error("Error creating model JSON", e);throw new ActivitiException("Error creating model JSON", e);}}return modelNode;}
}
  • ModelSaveRestResource
package com.oneconnect.sg.act.activiti;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;/*** @author Tijs Rademakers*/
@RestController
public class ModelSaveRestResource implements ModelDataJsonConstants {protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate ObjectMapper objectMapper;@RequestMapping(value="/model/{modelId}/save", method = RequestMethod.PUT)@ResponseStatus(value = HttpStatus.OK)public void saveModel(@PathVariable String modelId, @RequestParam MultiValueMap<String, String> values) {try {Model model = repositoryService.getModel(modelId);ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());modelJson.put(MODEL_NAME, values.getFirst("name"));modelJson.put(MODEL_DESCRIPTION, values.getFirst("description"));model.setMetaInfo(modelJson.toString());model.setName(values.getFirst("name"));// 每次修改模型保存后,将版本号+1model.setVersion(model.getVersion()+1);ObjectNode jsonXml = (ObjectNode) objectMapper.readTree(values.get("json_xml").get(0));model.setKey(jsonXml.get("properties").get("process_id").textValue());repositoryService.saveModel(model);repositoryService.addModelEditorSource(model.getId(), values.getFirst("json_xml").getBytes("utf-8"));InputStream svgStream = new ByteArrayInputStream(values.getFirst("svg_xml").getBytes("utf-8"));TranscoderInput input = new TranscoderInput(svgStream);PNGTranscoder transcoder = new PNGTranscoder();// Setup outputByteArrayOutputStream outStream = new ByteArrayOutputStream();TranscoderOutput output = new TranscoderOutput(outStream);// Do the transformationtranscoder.transcode(input, output);final byte[] result = outStream.toByteArray();repositoryService.addModelEditorSourceExtra(model.getId(), result);outStream.close();} catch (Exception e) {LOGGER.error("Error saving model", e);throw new ActivitiException("Error saving model", e);}}
}
  • StencilsetRestResource
package com.oneconnect.sg.act.activiti;import org.activiti.engine.ActivitiException;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;import java.io.InputStream;/*** @author Tijs Rademakers*/
@RestController
public class StencilsetRestResource {@RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")public @ResponseBody String getStencilset() {InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("static/stencilset.json");try {return IOUtils.toString(stencilsetStream, "utf-8");} catch (Exception e) {throw new ActivitiException("Error while loading stencil set", e);}}
}

将编辑器静态资源文件放在resources下面

2)创建模型

/*** 新增流程模型* @param req* @return* @throws Exception*/@Testpublic String add(ModelAddREQ req) throws Exception {int version = 0;// 1. 初始空的模型Model model = repositoryService.newModel();model.setName(req.getName());model.setKey(req.getKey());model.setVersion(version);// 封装模型json对象ObjectNode objectNode  = objectMapper.createObjectNode();objectNode.put(ModelDataJsonConstants.MODEL_NAME, req.getName());objectNode.put(ModelDataJsonConstants.MODEL_REVISION, version);objectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, req.getDescription());model.setMetaInfo(objectNode.toString());// 保存初始化的模型基本信息数据repositoryService.saveModel(model);// 封装模型对象基础数据json串// {"id":"canvas","resourceId":"canvas","stencilset":{"namespace":"http://b3mn.org/stencilset/bpmn2.0#"},"properties":{"process_id":"未定义"}}ObjectNode editorNode = objectMapper.createObjectNode();ObjectNode stencilSetNode = objectMapper.createObjectNode();stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");editorNode.replace("stencilset", stencilSetNode);// 标识keyObjectNode propertiesNode = objectMapper.createObjectNode();propertiesNode.put("process_id", req.getKey());editorNode.replace("properties", propertiesNode);repositoryService.addModelEditorSource(model.getId(), editorNode.toString().getBytes("utf-8"));return model.getId();}

将具体要绑定的业务bussinekey和routes绑定到此模型上

3) 查询流程模型模板

/*** 查询所有流程定义模板*/@Testpublic void modelList() {ModelQuery modelQuery = repositoryService.createModelQuery();List<Model> modelList = modelQuery.orderByCreateTime().desc().list();modelList.stream().forEach(m -> {System.out.print(" 模型id: " + m.getId());System.out.print(", 模型名称: " + m.getName());System.out.print(", 模型描述: " + m.getMetaInfo());System.out.print(", 模型标识key: " + m.getKey());System.out.print(", 模型版本号: " + m.getVersion());});}

4)删除流程定义模板

/*** 删除流程定义模板*/@Testpublic void deleteModel() {String modelId = "dc3m9c9b-1ea6-11ec-9a56-28d0ea7310b3";repositoryService.deleteModel(modelId);//ACT_RE_MODEL、ACT_GE_BYTEARRAYSystem.out.println("删除成功");}

5)导出模型zip方式

 /*** 将模型以zip的方式导出* @param modelId* @param response*/@Overridepublic void exportZip(HttpServletResponse response) {String modelId = "dc3m9c9b-1ea6-11ec-9a56-28d0ea7310b3";ZipOutputStream zipos = null;try {// 实例化zip输出流zipos = new ZipOutputStream(response.getOutputStream());// 压缩包文件名String zipName = "模型不存在";// 1. 查询模型基本信息Model model = repositoryService.getModel(modelId);if(model != null) {// 2. 查询流程定义模型的json字节码byte[] bpmnJsonBytes = repositoryService.getModelEditorSource(modelId);// 2.1 将json字节码转换为xml字节码byte[] xmlBytes = bpmnJsonXmlBytes(bpmnJsonBytes);if(xmlBytes == null) {zipName = "模型数据为空-请先设计流程定义模型,再导出";}else {// 压缩包文件名zipName = model.getName() + "." + model.getKey() + ".zip";// 将xml添加到压缩包中(指定xml文件名:请假流程.bpmn20.xml )zipos.putNextEntry(new ZipEntry(model.getName() + ".bpmn20.xml"));zipos.write(xmlBytes);// 3. 查询流程定义模型的图片字节码byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);if(pngBytes != null) {// 图片文件名(请假流程.leaveProcess.png)zipos.putNextEntry(new ZipEntry(model.getName() + "." + model.getKey() + ".png"));zipos.write(pngBytes);}}}response.setContentType("application/octet-stream");response.setHeader("Content-Disposition","attachment; filename=" + URLEncoder.encode(zipName, "UTF-8") + ".zip");// 刷出响应流response.flushBuffer();} catch (Exception e) {e.printStackTrace();} finally {if(zipos != null) {try {zipos.closeEntry();zipos.close();} catch (IOException e) {e.printStackTrace();}}}}

6)部署流程

 /*** 通过模型数据部署流程定义* @param modelId* @return* @throws Exception*/@Testpublic String deploy() throws Exception {String modelId = "dc3m9c9b-1ea6-11ec-9a56-28d0ea7310b3";// 1. 查询流程定义模型json字节码byte[] jsonBytes = repositoryService.getModelEditorSource(modelId);if(jsonBytes == null) {return "模型数据为空,请先设计流程定义模型,再进行部署";}// 将json字节码转为 xml 字节码,因为bpmn2.0规范中关于流程模型的描述是xml格式的,而activiti遵守了这个规范byte[] xmlBytes = bpmnJsonXmlBytes(jsonBytes);if(xmlBytes == null) {return "数据模型不符合要求,请至少设计一条主线流程";}// 2. 查询流程定义模型的图片byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);// 查询模型的基本信息Model model = repositoryService.getModel(modelId);// xml资源的名称 ,对应act_ge_bytearray表中的name_字段String processName = model.getName() + ".bpmn20.xml";// 图片资源名称,对应act_ge_bytearray表中的name_字段String pngName = model.getName() + "." + model.getKey() + ".png";// 3. 调用部署相关的api方法进行部署流程定义Deployment deployment = repositoryService.createDeployment().name(model.getName()) // 部署名称.addString(processName, new String(xmlBytes, "UTF-8")) // bpmn20.xml资源.addBytes(pngName, pngBytes) // png资源.deploy();// 更新 部署id 到流程定义模型数据表中model.setDeploymentId(deployment.getId());repositoryService.saveModel(model);return "部署成功";}

5.3 流程操作—流程部署

一个流程部署有两种方式,一是通过程定义模型数据,部署流程定义;二是通过 .zip 流程压缩包进行部署的流程定义。

  • 通过流程定义模型数据,部署流程定义
 @Testpublic void deploy() throws Exception {//1、查询流程定义模型的json字节码// 将json字节码转为xml字节码,因为bpmn2.0规范中关于流程模型的描述是xml格式的,而activiti遵守了这个规范String modelId = "dc3m9c9b-1ea6-11ec-9a56-28d0ea7310b3";byte[] jsonBytes = repositoryService.getModelEditorSource(modelId);if (jsonBytes == null) {System.out.println("模型数据为空,请先设计流程定义模型,再进行部署");return;}JsonNode jsonNode = objectMapper.readTree(jsonBytes);BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);byte[] xmlByte = null;if (bpmnModel.getProcesses().size() != 0) {xmlByte = new BpmnXMLConverter().convertToXML(bpmnModel);if (xmlByte == null) {System.out.println("数据模型不符合要求,请至少设计一条主线流程");return;}}//2、查询流程定义模型的图片byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);// 查询模型的基本信息Model model = repositoryService.getModel(modelId);// xml资源的名称,对应ACT_GE_BYTEARRAY表中的name_字段String processName = model.getName() + ".bpmn20.xml";// 图片资源名称,对应ACT_GE_BYTEARRAY表中的name_字段String pngName = model.getName() + "." + model.getKey() + ".png";//3、调用部署相关的api方法进行部署流程定义// 操作一下表// ACT_RE_PROCDEF 新增数据: 流程定义数据// ACT_RE_DEPLOYMENT 新增数据: 流程部署数据// ACT_GE_BYTEARRAY 新增数据:将当前流程图绑定到此流程定义部署数据上Deployment deployment = repositoryService.createDeployment().name(model.getName())  //部署名称.addString(processName, new String(xmlByte)) //bpmn2.0资源.addBytes(pngName, pngBytes) //png资源.deploy();// 更新 部署id 到流程定义模型表中// ACT_RE_MODEL 更新部署idmodel.setDeploymentId(deployment.getId());repositoryService.saveModel(model);System.out.println("部署成功");}
  • 通过 .zip 流程压缩包进行部署的流程定义
 @Testpublic void deployByZip() throws Exception {File file = new File("D:/请假流程test01.leave.zip");String filename = file.getName();// 压缩包输入流ZipInputStream zipis = new ZipInputStream(new FileInputStream(file));// 创建部署实例DeploymentBuilder deployment = repositoryService.createDeployment();// 添加zip流deployment.addZipInputStream(zipis);// 部署名称deployment.name(filename.substring(0, filename.indexOf(".")));// 执行部署流程定义deployment.deploy();System.out.println("zip压缩包方式部署流程定义完成");}

2)删除部署信息

 /*** 根据部署ID删除流程定义部署信息:* ACT_GE_BYTEARRAY、* ACT_RE_DEPLOYMENT、* ACT_RE_PROCDEF、* -----下面是流程实例时产生的数据会被一起删除* ACT_RU_IDENTITYLINK、* ACT_RU_EVENT_SUBSCR*/@Testpublic void delete() {try {// 部署ID ACT_RE_DEPLOYMENTString deploymentId = "d298a2f5-1db4-11ec-9048-28d0ea7310b3";// 不带级联的删除:如果有正在执行的流程,则删除失败抛出异常;不会删除 ACT_HI_和 历史表数据repositoryService.deleteDeployment(deploymentId);// 级联删除:不管流程是否启动,都能可以删除;并删除历史表数据。//repositoryService.deleteDeployment(deploymentId, true);System.out.println("删除流程定义部署信息成功");} catch (Exception e) {e.printStackTrace();if (e.getMessage().indexOf("a foreign key constraint fails") > 0) {System.out.println("有正在执行的流程,不允许删除");} else {System.out.println("删除失败,原因:" + e.getMessage());}}}

3)查询部署的流程定义数据

/*** 查询部署的流程定义数据 ACT_RE_PROCDEF* 需求:如果有多个相同流程定义标识key的流程时,只查询其最新版本*/@Testpublic void getProcessDefinitionList() {// 1. 获取 ProcessDefinitionQueryProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();// 条件查询query.processDefinitionNameLike("%请假%");// 有多个相同标识key的流程时,只查询其最新版本query.latestVersion();// 按流程定义key升序排列query.orderByProcessDefinitionKey().asc();// 当前查询第几页int current = 1;// 每页显示多少条数据int size = 5;// 当前页第1条数据下标int firstResult = (current - 1) * size;// 开始分页查询List<ProcessDefinition> definitionList = query.listPage(firstResult, size);for (ProcessDefinition pd : definitionList) {System.out.print("流程部署ID:" + pd.getDeploymentId());System.out.print(",流程定义ID:" + pd.getId());System.out.print(",流程定义Key:" + pd.getKey());System.out.print(",流程定义名称:" + pd.getName());System.out.print(",流程定义版本号:" + pd.getVersion());System.out.println(",状态:" + (pd.isSuspended() ? "挂起(暂停)" : "激活(开启)"));}// 用于前端显示页面,总记录数long total = query.count();System.out.println("满足条件的流程定义总记录数:" + total);}

4)挂起或者激活流程定义

/*** 通过流程定义id,挂起或激活流程定义*/@Testpublic void updateProcessDefinitionState() {// 流程定义ID ACT_RE_PROCDEFString definitionId = "leave:2:b9227310-c8f4-11eb-acbf-aa2e2e85fc05";// 流程定义对象ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(definitionId).singleResult();// 获取当前状态是否为:挂起boolean suspended = processDefinition.isSuspended();//对应 act_re_procdef 表中的 SUSPENSION_STATE_ 字段,1是激活,2是挂起if (suspended) {// 如果状态是:挂起,将状态更新为:激活,// 参数1: 流程定义id;参数2:是否级联激活该流程定义下的流程实例;参考3:设置什么时间激活这个流程定义,如果 null 则立即激活)repositoryService.activateProcessDefinitionById(definitionId, true, null);} else {// 如果状态是:激活,将状态更新为:挂起// 参数 (流程定义id,是否挂起,激活时间)repositoryService.suspendProcessDefinitionById(definitionId, true, null);}}

5)导出流程定义相关文件

/*** 导出下载流程定义相关的文件(.bpmn20.xml流程描述或.png图片资源)* @throws Exception*/@Testpublic void exportProcDefFile() throws Exception {// 流程定义id ACT_RE_PROCDEFString processDefinitionId = "leave:2:4ccffb81-1b68-11ec-9d87-28d0ea7310b3";ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);//获取的是 xml 资源名String resourceName = processDefinition.getResourceName();//获取的是 png 资源名String diagramResourceName = processDefinition.getDiagramResourceName();//查询到相关的资源输入流InputStream input = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);File file = new File("D:/" + resourceName);FileOutputStream out = new FileOutputStream(file);// 输入流,输出流的转换IOUtils.copy(input, out);// 关闭流out.close();input.close();System.out.println("下载流程定义 资源(xml、png) 文件成功");}

5.4 流程操作—流程实例

1)启动流程

/*** 启动流程实例* ACT_HI_TASKINST 任务实例* ACT_HI_PROCINST 流程实例* ACT_HI_ACTINST  节点实例* ACT_HI_IDENTITYLINK  流程实例相关办理人* ACT_RU_EXECUTION  运行时流程执行实例表* ACT_RU_TSK 运行时流程任务实例* ACT_RU_IDENTITYLINK 运行时流程实例相关办理人*/@Testpublic void startProcessInstance() {// 流程定义唯一标识key  ACT_RE_PROCDEF 字段KEY_String processKey = "leave";//业务idString businessKey = "10000";//启动当前流程实例的用户Authentication.setAuthenticatedUserId("zhangsan");//启动流程实例(流程定义唯一标识key,业务id),采用流程key对应的最新版本的流程定义数据ProcessInstance pi = runtimeService.startProcessInstanceByKey(processKey, businessKey);//将流程定义名称 作为 流程实例名称runtimeService.setProcessInstanceName(pi.getProcessInstanceId(), pi.getProcessDefinitionName());System.out.println("启动流程实例成功: " + pi.getProcessInstanceId());}

启动流程实例时可以将业务配置绑定到当前流程上,办理人以实际业务办理人或候选人

2)查询正在运行的流程实例

/*** 查询正在运行中的流程实例*/@Testpublic void getProcInstListRunning() {//        //查询正在运行的流程实例 所有
//        List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();//查询正在运行的流程实例 按照条件查询List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().processInstanceNameLike("%请假%").list();list.stream().forEach(l -> {System.out.println("流程定义key: " + l.getProcessDefinitionKey());System.out.println("流程定义版本号: " + l.getProcessDefinitionVersion());System.out.println("流程实例Id: " + l.getProcessInstanceId());System.out.println("流程实例名称: " + l.getName());System.out.println("业务key(业务主键id): " + l.getBusinessKey());System.out.println("发起人: " + l.getStartUserId());System.out.println("流程实例状态: " + (l.isSuspended() ? "已挂起(暂停)" : "已激活(启动)"));});}

当前运行实例只有一个任务在运行

3)挂起或者激活流程实例

/*** 挂起或激活流程实例*/@Testpublic void updateProInstState() {//流程实例id ACT_RU_EXECUTION 字段 ROOT_PROC_INST_ID_String proInstId = "d2f5ca6c-1c01-11ec-ba31-28d0ea7310b3";//查询流程实例对象ProcessInstance processInstance =runtimeService.createProcessInstanceQuery().processInstanceId(proInstId).singleResult();//获取当前流程实例状态是否为:挂起(暂停)boolean suspended = processInstance.isSuspended();//判断if (suspended) {//如果状态是:挂起,则更新为激活状态 ACT_RU_EXECUTION 字段 SUSPENSION_STATE_  1 激活runtimeService.activateProcessInstanceById(proInstId);System.out.println("激活流程实例");} else {//如果状态是:激活,则更新为挂起状态 CT_RU_EXECUTION 字段 SUSPENSION_STATE_  2 挂起runtimeService.suspendProcessInstanceById(proInstId);System.out.println("挂起流程实例");}}

4)删除流程实例

/*** 删除流程实例* ACT_RU_IDENTITYLINK* ACT_RU_INTEGRATION* ACT_RU_TASK*/@Testpublic void deleteProcInst() {//流程实例id ACT_RU_EXECUTION 字段 ROOT_PROC_INST_ID_String proInstId = "7bf4bfe2-1db6-11ec-ba03-28d0ea7310b3";//查询流程实例对象ProcessInstance processInstance =runtimeService.createProcessInstanceQuery().processInstanceId(proInstId).singleResult();if (processInstance == null) {System.out.println("该实例不存在");return;}//删除流程实例(流程实例id,删除原因),不会删除流程实例相关历史数据runtimeService.deleteProcessInstance(proInstId, "XXX作废了当前流程申请");//删除流程实例相关历史数据historyService.deleteHistoricProcessInstance(proInstId);}

5)查询已结束流程实例

/*** 查询已结束的流程实例** @param req* @return*/@Testpublic void getProcInstFinish() {HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().finished() //已结束.orderByProcessInstanceEndTime().desc();List<HistoricProcessInstance> list =query.list();list.stream().forEach(l -> {System.out.println(l.getId());//流程实例idSystem.out.println(l.getName());//流程名称System.out.println(l.getProcessDefinitionKey());//流程定义keySystem.out.println(l.getProcessDefinitionVersion());//流程定义版本System.out.println(l.getStartUserId());//流程发起人System.out.println(l.getBusinessKey());//业务IDSystem.out.println(l.getStartTime()));//流程实例开始时间System.out.println(l.getEndTime()));//流程实例结束时间System.out.println(l.getDeleteReason());//删除原因});}

5.5 流程操作—流程任务

1)查询当前用户办理人或候选人任务

 /*** 查询当前用户是办理人或候选人的待办任务 pc** @param req* @return*/@Testpublic void findWaitTask() {//办理人(当前用户)String assignee = "1132";TaskQuery query = taskService.createTaskQuery().taskCandidateOrAssigned(assignee)// 作为办理人或候选人.orderByTaskCreateTime().desc();if (StringUtils.isNotEmpty(req.getTaskName())) {query.taskNameLike("%" + req.getTaskName() + "%");}//分页查询List<Task> taskList = query.list;taskList.stream().forEach(l -> {System.out.println(l.getId());//任务IDSystem.out.println(l.getName());//任务名称System.out.println(l.isSuspended() );//状态System.out.println(l.getAssignee());//流程定义版本System.out.println(l..getAssignee());//办理人System.out.println(l.getProcessInstanceId());//流程实例IDSystem.out.println(l.getCreateTime()));//任务创建时System.out.println(l.getExecutionId());//执行对象IDSystem.out.println(l.getProcessDefinitionId());//流程定义ID});

2)获得下一节点任务

@Tes
public List<Map<String, Object>> getNextNodeInfo() {String taskId = "b10ec157-1c78-11ec-93ec-28d0ea7310b3"Task task = taskService.createTaskQuery().taskId(taskId).singleResult();if (task == null) {return null;}//获取当前模型BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());//根据任务节点id获取当前节点FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());//封装下一个节点信息List<Map<String, Object>> nextNodes = new ArrayList<>();getNextNodes(flowElement, nextNodes);return nextNodes;}

获取当前审批节点,用来设置下一节点办理人,如果有排他或并行网关,需要获取其集合

/*** 判断当前节点的下一节点是人工任务的集合** @param flowElement 当前节点* @param nextNodes   下节点名称集合*/public void getNextNodes(FlowElement flowElement, List<Map<String, Object>> nextNodes) {//获取当前节点的连线信息List<SequenceFlow> outgoingFlows = ((FlowNode) flowElement).getOutgoingFlows();for (SequenceFlow outgoingFlow : outgoingFlows) {//下一节点FlowElement nextFlowElement = outgoingFlow.getTargetFlowElement();if (nextFlowElement instanceof EndEvent) {//结束节点break;} else if (nextFlowElement instanceof UserTask) {Map<String, Object> node = new HashMap<>();//用户任务 获取节点id和名称node.put("id", nextFlowElement.getId());node.put("name", nextFlowElement.getName());nextNodes.add(node);} else if (nextFlowElement instanceof ParallelGateway ||  //并行网关nextFlowElement instanceof ExclusiveGateway) { //排他网关//递归 继续找下一节点getNextNodes(nextFlowElement, nextNodes);}}}

3)拾取任务

/*** 签收(拾取)任务*/@Testpublic String claimTask() {String taskId = "b10ec157-1c78-11ec-93ec-28d0ea7310b3";String userId = "zhangsan";Task task = taskService.createTaskQuery().taskId(taskId).singleResult();if (StringUtils.isBlank(task.getAssignee())) {taskService.claim(taskId, userId);return "操作成功";} else {return "操作失败";}}

这里取消拾取任务只需要在claim时将user置为null,就可以取消拾取任务

4)任务转交

/*** 任务转交*/@Testpublic void turnTask() {String taskId = "b10ec157-1c78-11ec-93ec-28d0ea7310b3";String assigneeUserKey = "校长";String userId = "zhangsan";Task task = taskService.createTaskQuery().taskId(taskId).singleResult();taskService.setAssignee(taskId, assigneeUserKey)//添加处理意见taskService.addComment(taskId, task.getProcessInstanceId(), message);}

5)完成任务

 /*** 执行任务*/@Testpublic void completeTask() {//每个节点就是一个任务, ACT_RU_TASK  ID_String taskId = "b10ec157-1c78-11ec-93ec-28d0ea7310b3";//执行任务 complete 这个方法还有别的参数, complete(String taskId, Map<String, Object> variables);//variables 表示流程变量,可以修改,覆盖之前流程实例初始化时的流程变量taskService.complete(taskId);}

6)设置下一节点人

/*** 完成当前节点时,设置下一节点任务办理人(上面输入框指定的那个办理人)*/@Testpublic void completeTaskSetNextAssignee() {String taskId = "ab7567f5-1cfb-11ec-a067-28d0ea7310b3";Task task = taskService.createTaskQuery().taskId(taskId).singleResult();if (task == null) {System.out.println("任务id错误,无法查询到相关任务");} else {//执行任务taskService.complete(taskId);//查询下一个任务List<Task> taskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();//下一个节点任务if (!CollectionUtils.isEmpty(taskList)) {//针对每个任务分配审批人for (Task t : taskList) {//当前任务有审批人,则不设置新的审批人if (StringUtils.isNotEmpty(t.getAssignee())) {System.out.println("当前任务有审批人,不设置新的审批人");continue;}//分配新的审批人String assignee = "lisi";taskService.setAssignee(t.getId(), assignee);}}}}

7)获取已完成任务用于节点跳转

 @Testpublic List<Map<String, Object>> getBackNodes() {String taskId = "ab7567f5-1cfb-11ec-a067-28d0ea7310b3";String userId = "zhangsan";Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(username).singleResult();if (task == null) {return null;}// 不把当前节点查询出来, 没有办理完的节点不查询,每条数据都有一个唯一值,我们使用随机数String sql = "select rand() AS ID_, t2.* from " +" ( select distinct t1.TASK_DEF_KEY_, t1.NAME_ from " +"  ( select ID_, RES.TASK_DEF_KEY_, RES.NAME_, RES.START_TIME_, RES.END_TIME_ " +"   from ACT_HI_TASKINST RES " +"   WHERE RES.PROC_INST_ID_ = #{processInstanceId} and TASK_DEF_KEY_ != #{taskDefKey}" +"   and RES.END_TIME_ is not null order by RES.START_TIME_ asc " +"  ) t1 " +" ) t2";List<HistoricTaskInstance> list =historyService.createNativeHistoricTaskInstanceQuery().sql(sql).parameter("processInstanceId", task.getProcessInstanceId()).parameter("taskDefKey", task.getTaskDefinitionKey()).list();List<Map<String, Object>> list = new ArrayList<>();list.forEach(hit -> {Map<String, Object> data = new HashMap<>();data.put("activityId", hit.getTaskDefinitionKey());data.put("activityName", hit.getName());list.add(data);});return list;}

8)节点跳转

/*** 1. 取得当前节点信息* 2. 获取驳回的目标节点信息* 要考虑并行网关:从选中的目标节点的上级节点(如:并行网关),找到其上级节点的所有子节点,并行 网关就会有多条子节点* 3. 将当前节点出口指定为驳回的目标节点,(并行网关是多条)* 4. 完成当前节点任务,删除执行表 is_active_=0数据,不然并行汇聚不向后流转;删除其他并行任务,* 5. 分配目标节点原办理人。** @param taskId           当前任务id* @param targetActivityId 回滚节点 TaskDefinitionKey  (getBackNodes这个方法设置)* @param userId* @return*/@Testpublic void backProcess(String taskId, , String userId) {String taskId = "ab7567f5-1cfb-11ec-a067-28d0ea7310b3";String userId = "zhangsan";String targetActivityId = "fb7643c2-01ea-08ac-b098-82c0da3710f3"//----------------------第一部分// 1. 查询当前任务信息Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(username).singleResult();if (task == null) {return "Error:当前任务不存在或你不是任务办理人";}// 2. 获取流程模型实例 BpmnModelBpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());// 3. 当前节点信息FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());// 4. 获取当前节点的原出口连线List<SequenceFlow> sequenceFlowList = curFlowNode.getOutgoingFlows();// 5. 临时存储当前节点的原出口连线List<SequenceFlow> oriSequenceFlows = new ArrayList<>();oriSequenceFlows.addAll(sequenceFlowList);// 6. 将当前节点的原出口清空sequenceFlowList.clear();//----------------------第二部分// 7. 获取目标节点信息FlowNode targetFlowNode = (FlowNode) bpmnModel.getFlowElement(targetActivityId);// 8. 获取驳回的新节点// 获取目标节点的入口连线List<SequenceFlow> incomingFlows = targetFlowNode.getIncomingFlows();// 存储获取驳回的新的流向List<SequenceFlow> allSequenceFlow = new ArrayList<>();for (SequenceFlow incomingFlow : incomingFlows) {// 找到入口连线的源头(获取目标节点的父节点)FlowNode source = (FlowNode) incomingFlow.getSourceFlowElement();// 获取目标节点的父组件的所有出口,List<SequenceFlow> sequenceFlows;if (source instanceof ParallelGateway) {// 并行网关的出口有多条连线:根据目标入口连线的父节点的出口连线,其所有出口连线才是驳回的真实节点sequenceFlows = source.getOutgoingFlows();} else {// 其他类型,将目标入口作为当前节点的出口sequenceFlows = targetFlowNode.getIncomingFlows();}// 找到后把它添加到集合作为新方向allSequenceFlow.addAll(sequenceFlows);}// 9. 将当前节点的出口设置为新节点curFlowNode.setOutgoingFlows(allSequenceFlow);//----------------------第三部分//流程实例idString procInstId = task.getProcessInstanceId();// 10. 完成当前任务,流程就会流向目标节点创建新目标任务//    删除已完成任务,删除已完成并行任务的执行数据 act_ru_executionList<Task> list = taskService.createTaskQuery().processInstanceId(procInstId).list();list.forEach(t -> {// 当前任务idif (taskId.equals(t.getId())) {// 当前任务,完成当前任务String message = String.format("【%s 驳回任务 %s => %s】",username, task.getName(), targetFlowNode.getName());taskService.addComment(t.getId(), procInstId, message);// 完成任务,就会进行驳回到目标节点,产生目标节点的任务数据taskService.complete(taskId);// 删除执行表中 is_active_ = 0的执行数据, 使用command自定义模型DelelteExecutionCommand deleteExecutionCMD = new DelelteExecutionCommand(task.getExecutionId());managementService.executeCommand(deleteExecutionCMD);} else {// 删除其他未完成的并行任务// taskService.deleteTask(taskId); // 注意这种方式删除不掉,会报错:流程正在运行中无法删除。// 使用command自定义命令模型来删除,直接操作底层的删除表对应的方法,对应的自定义是否删除DeleteTaskCommand deleteTaskCMD = new DeleteTaskCommand(t.getId());managementService.executeCommand(deleteTaskCMD);}});// 11. 查询目标任务节点历史办理人List<Task> newTaskList = taskService.createTaskQuery().processInstanceId(procInstId).list();for (Task newTask : newTaskList) {// 取之前的历史办理人HistoricTaskInstance oldTargerTask = historyService.createHistoricTaskInstanceQuery().taskDefinitionKey(newTask.getTaskDefinitionKey()) // 节点id.processInstanceId(procInstId)// .finished() // 已经完成才是历史.processFinished().orderByTaskCreateTime().desc() // 最新办理的在最前面.list().get(0);taskService.setAssignee(newTask.getId(), oldTargerTask.getAssignee());}//----------------------第四部分// 12. 完成驳回功能后,将当前节点的原出口方向进行恢复curFlowNode.setOutgoingFlows(oriSequenceFlows);}

5.6 流程操作—流程历史

1)查询审批历史记录

@Test
public void getHistoryInfoList() {String procInstId = "ab7567f5-1cfb-11ec-a067-28d0ea7310b3";//查询流程人工任务历史数据List<HistoricActivityInstance> historicActivityInstances =historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();for (HistoricTaskInstance hti : historicTaskInstanceList) System.out.println(hti.getId()); //任务idSystem.out.println(hti.getName()); //任务名称System.out.println(hti.getProcessInstanceId()); //流程实例IDSystem.out.println(hti.getStartTime()); //开始时间System.out.println(hti.getEndTime()); //结束时间System.out.println(hti.getAssignee()); //办理人}

这里也可以配置业务的businessKey来查询相应的历史任务
2)获取审批历史记录图

/*** 获取流程实例审批历史*/@Testpublic void getHistoryProcessImage(HttpServletResponse response) {String procInstId = "ab7567f5-1cfb-11ec-a067-28d0ea7310b3", InputStream imageStream = null;try {// 通过流程实例ID获取历史流程实例HistoricProcessInstance historicProcessInstance =historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();// 获取流程定义Model对象BpmnModel bpmnModel =repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());// 创建流程图生成器CustomProcessDiagramGenerator generator = new CustomProcessDiagramGenerator();//通过流程实例ID获取流程中已经执行的节点,按照执行先后顺序排序List<HistoricActivityInstance> historicActivityInstances =historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId).orderByHistoricActivityInstanceStartTime().desc().list();// 将已经执行的节点id放入高亮显示节点集合List<String> highLightedActivityIdList =historicActivityInstances.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());// 通过流程实例ID获取流程中正在执行的节点List<Execution> runningActivityInstanceList =runtimeService.createExecutionQuery().processInstanceId(procInstId).list();List<String> runningActivityIdList = new ArrayList<>();for (Execution execution : runningActivityInstanceList) {if (!StringUtils.isEmpty(execution.getActivityId())) {runningActivityIdList.add(execution.getActivityId());}}// 获取已经流经的流程线,需要高亮显示流程已经发生流转的线id集合List<String> highLightedFlowsIds =generator.getHighLightedFlows(bpmnModel, historicActivityInstances);// 使用自定义配置获得流程图表生成器,并生成追踪图片字符流imageStream =generator.generateDiagramCustom(bpmnModel,highLightedActivityIdList,runningActivityIdList,highLightedFlowsIds,"宋体","微软雅黑","黑体");// 输出资源内容到相应对象response.setContentType("image/svg+xml");byte[] bytes = IOUtils.toByteArray(imageStream);OutputStream outputStream = response.getOutputStream();outputStream.write(bytes);outputStream.flush();outputStream.close();} catch (Exception e) {e.printStackTrace();} finally {if (imageStream != null) {try {imageStream.close();} catch (IOException e) {e.printStackTrace();}}}}

3)查询指定用户的已处理任务

 @Testpublic void findCompleteTask() {//用户idString userId = "meng";List<HistoricTaskInstance> historicTaskInstances =historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).orderByTaskCreateTime().desc().finished().list();historicTaskInstances.forEach(h -> {System.out.println("任务id:" + h.getId());System.out.println("任务名称:" + h.getName());System.out.println("任务的办理任:" + h.getAssignee());System.out.println("任务的开始时间:" + h.getStartTime());System.out.println("任务的结束时间" + h.getEndTime());System.out.println("流程实例ID:" + h.getProcessInstanceId());System.out.println("流程定义ID:" + h.getProcessDefinitionId());System.out.println("业务唯一标识:" + h.getBusinessKey());});}

4)删除已结束的流程实例

/*** 删除已结束的流程实例* ACT_HI_DETAIL* ACT_HI_VARINST* ACT_HI_TASKINST* ACT_HI_PROCINST* ACT_HI_ACTINST* ACT_HI_IDENTITYLINK* ACT_HI_COMMENT*/@Testpublic void deleteFinishProcInst() {String procInstId = "4c772281-1c75-11ec-b888-28d0ea7310b3";//查询流程实例是否已结束HistoricProcessInstance historicProcessInstance =historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).finished().singleResult();if (historicProcessInstance == null) {System.out.println("流程实例不存在或未结束");return;}historyService.deleteHistoricProcessInstance(procInstId);}

六、Activiti监听器

6.1 监听器分配

监听器分配,分配的是什么,是任务执行人的分配。

  • Event(Create、Assignment、Delete、All)
  • Create: 任务创建后触发
  • Assignment:任务分配后触发
  • Delete: 任务完成后触发
  • All: 所有事件发生都触发
    在我们绘制流程图时,可以对这个岗位进行事件监听,设置它触发条件,当节点任务发生变化时做出相应的处理
  • 使用监听器需要实现TaskListener接口
public class ManagementTakListener implements TaskListener {@Overridepublic void notify(DelegateTask delegateTask) {//任务idString taskId = delegateTask.getId();//监听事件 create\delete\assignment\completeString eventName = delegateTask.getEventName();HistoryService historyService =SpringUtil.getBean(HistoryServiceImpl.class);//任务创建时执行if ("班主任".equals(delegateTask.getName())&& "create".equalsIgnoreCase(delegateTask.getEventName())) String processInstanceId = delegateTask.getProcessInstanceId()HistoricProcessInstance historicProcessInstance =historyService.createHistoricProcessInstanceQuery().processInstanceId(delegateTask.getProcessInstanceId()).singleResult();String userId = historicProcessInstance.getStartUserId();delegateTask.addCandidateUser(userId);}}//任务完成时if ("班主任".equals(delegateTask.getName())&& "complete".equalsIgnoreCase(delegateTask.getEventName())) {HistoricProcessInstance historicProcessInstance =historyService.createHistoricProcessInstanceQuery().processInstanceId(delegateTask.getProcessInstanceId()).singleResult();//TODO审批意见回调}}

七、Activiti安全验证

7.1 去除activiti内置的sercurity

acitiviti模型设计器整合到Spring boot原工程中时需要排除security安全验证,启动项目后访问所有界面都被拦截到登录界面

  • 没有排除security安全验证出现错误如下
Caused by: java.io.FileNotFoundException: class path
resource [org/springframework/security/config/annotation/authentication
/configurers/GlobalAuthenticationConfigurerAdapter.class]
cannot be opened because it does not exist
  • 去除方法
    1)在启动类上加上如下注解
    2)继承WebSecurityConfigurerAdapter

@SpringBootApplication(exclude ={org.activiti.spring.boot.SecurityAutoConfiguration.class,SecurityAutoConfiguration.class,ManagementWebSecurityAutoConfiguration.class})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/**").permitAll();
}

八、总结

总结来说工作流的工作流程从流程模型—>流程部署—>流程启动。在配置模型时可以配置业务路由绑定实际业务,在流程启动时可以设置业务办理人。通过模型ID到流程定义ID,从流程定义ID到流程实例ID,从流程实例ID获取当前实例任务ID,历史节点信息,从而了解整个流程的流动方向和流动状态,流程定义绑定业务businessKey,从而嵌入实际业务逻辑。

Activiti7工作流流程详解相关推荐

  1. 基于spark mllib_Spark高级分析指南 | 机器学习和分析流程详解(下)

    - 点击上方"中国统计网"订阅我吧!- 我们在Spark高级分析指南 | 机器学习和分析流程详解(上)快速介绍了一下不同的高级分析应用和用力,从推荐到回归.但这只是实际高级分析过程 ...

  2. 跨境电商三单对碰三单申报流程详解

    跨境电商三单对碰三单申报流程详解 概要:三单申报是指"电子订单.电子运单.支付凭证". 1.电子订单: 适合申报企业类型"电商企业.电商交易平台.电商境内代理企业&quo ...

  3. Android事件流程详解

    Android事件流程详解 网络上有不少博客讲述了android的事件分发机制和处理流程机制,但是看过千遍,总还是觉得有些迷迷糊糊,因此特地抽出一天事件来亲测下,向像我一样的广大入门程序员详细讲述an ...

  4. View的绘制-draw流程详解

    目录 作用 根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来. 具体分析 以下源码基于版本27 DecorView 的draw 流程 在<Vi ...

  5. 杂志订阅管理系统c++_电池管理系统BMS功能安全开发流程详解

    点击上面 "电动知家"可以订阅哦! BMS功能安全开发流程详解 BMS和ISO26262 - BMS & ISO26262简介 BMS即Battery Management ...

  6. View的绘制-layout流程详解

    目录 作用 根据 measure 测量出来的宽高,确定所有 View 的位置. 具体分析 View 本身的位置是通过它的四个点来控制的: 以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删 ...

  7. U-Boot启动流程详解

    参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...

  8. java isight zmf_isight集成catia和abaqus,nastran流程详解

    isight集成catia和abaqus,nastran流程详解 CAD软件中参数化建模,导入有限元软件中计算各个工况,isight根据计算结果调整模型参数,反复迭代计算的过程是尺寸优化的典型问题~ ...

  9. java处理请求的流程_Java Spring mvc请求处理流程详解

    Spring mvc请求处理流程详解 前言 spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把.但是感觉讲的都不是很细致,让很多初学者都云里雾里的.本人也是这样,之前研究过, ...

最新文章

  1. 分享Leangoo敏捷工具操作视频
  2. 请写出一段Python代码实现删除一个list里面的重复元素
  3. [linux-nopage]内存映射虚拟字符设备驱动【P119】
  4. Java 泛形通配符 ?
  5. 思考:日期类型的数据应该用什么样的具体形式存储到数据库?
  6. Sharding-JDBC(操作公共表)_Sharding-Sphere,Sharding-JDBC分布式_分库分表工作笔记013
  7. SAP BASIS 工作台请求和定制请求的区别
  8. VC++中按钮,文本框,选择框的常用方法
  9. fseek函数c语言_使用示例的C语言中的fseek()函数
  10. ArduPilot日志系统探索(一)
  11. 热点的ap频段哪个快_热点ap频段有什么区别
  12. CAD一键统计所有线段长度
  13. 精力管理指南:想要每天精力充沛,只需做好这 3 点
  14. C语言每日一练——第12天:求一个矩阵的转置
  15. [Unity][Crowd]学习人群模拟资源分享以及相关的问题
  16. 刷屏的海底捞超级APP究竟是怎样与阿里云合作的
  17. word文档中英文单词之间空格间距过大问题如何解决
  18. Chrome 插件(Shockwave Flash)未响应 错误解决办法
  19. “冰封”合约背后的老牌劲敌——拒绝服务漏洞 | 漏洞解析连载之二
  20. vue实现就诊人信息的增删改查

热门文章

  1. 魏振瀛民法学笔记汇总整理
  2. DX-BT07S蓝牙调试问题记录
  3. 天然产物,抗病毒的利器
  4. python找出1000以内所有素数_python实现输出1到1000以内的素数
  5. 新的工作,新的旅途。
  6. CSV多标签数据预处理+加上csv多标签转文件夹+ ISIC2018数据集分类预处理
  7. 尾涡流matlab仿真,电涡流检测仿真软件的设计(附MATLAB程序)(测控技术)☆
  8. wifi模块整理 和 adb整理
  9. iOS NSInteger 的输出 %d %ld %zd %ld (long)
  10. CakePHP 2.x CookBook 中文版 第五章 控制器