springboot整合activiti流程设计器_git 工作流

springboot整合activiti流程设计器_git 工作流Activiti工作流使用之SpringBoot整合Activiti文章目录Activiti工作流使用之SpringBoot整合Activiti一、springboot整合Activiti环境依赖1.1maven环境1.2添加日志配置1.3添加activiti配置文件二、流程操作–流程模型2.1绘制流程模型编辑器2.2创建模型2.3查询流程模型模板2.4删除流程定义模板2.5导出模型zip方式2.6部署流程三、流程操作–流程部署3.1部署流程3.2删除部署信息3.3查询部署的流

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

Activiti工作流使用之SpringBoot整合Activiti

一、springboot整合Activiti环境依赖

1.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>
1.2 添加日志配置
##我们使用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
1.3 添加activiti配置文件

直接使用application.yaml配置

server:
port: 9889
servlet:
context-path: /com/act
spring:
mvc:
static-path-pattern: /**
cloud:
nacos:
discovery:
server-addr: 填写自己地址
datasource:
druid:
配置自己数据库连接
# 初始化大小,最小,最大
initial-size: 1
min-idle: 3
max-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-fail
logging:
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

二、流程操作–流程模型

2.1 绘制流程模型编辑器
  • 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;
/** * */
@RestController
public class ModelEditorJsonRestResource implements ModelDataJsonConstants { 

protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private 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;
/** * */
@RestController
public class ModelSaveRestResource implements ModelDataJsonConstants { 

protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private 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"));
// 每次修改模型保存后,将版本号+1
model.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 output
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
// Do the transformation
transcoder.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;
/** * */
@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.2 创建模型
/** * 新增流程模型 * @param req * @return * @throws Exception */
@Test
public 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);
// 标识key
ObjectNode 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绑定到此模型上

2.3 查询流程模型模板
/** * 查询所有流程定义模板 */
@Test
public 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());
});
}
2.4 删除流程定义模板
/** * 删除流程定义模板 */
@Test
public void deleteModel() { 

String modelId = "dc3m9c9b-1ea6-11ec-9a56-28d0ea7310b3";
repositoryService.deleteModel(modelId);
//ACT_RE_MODEL、ACT_GE_BYTEARRAY
System.out.println("删除成功");
}
2.5 导出模型zip方式
/** * 将模型以zip的方式导出 * @param modelId * @param response */
@Override
public 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();
}
}
}
}
2.6 部署流程
/** * 通过模型数据部署流程定义 * @param modelId * @return * @throws Exception */
@Test
public 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 "部署成功";
}

三、流程操作–流程部署

3.1 部署流程
  • 通过流程定义模型数据,部署流程定义
	@Test
public 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 更新部署id
model.setDeploymentId(deployment.getId());
repositoryService.saveModel(model);
System.out.println("部署成功");
}
  • 通过 .zip 流程压缩包进行部署的流程定义
	@Test
public 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压缩包方式部署流程定义完成");
}
3.2 删除部署信息
	/** * 根据部署ID删除流程定义部署信息: * ACT_GE_BYTEARRAY、 * ACT_RE_DEPLOYMENT、 * ACT_RE_PROCDEF、 * -----下面是流程实例时产生的数据会被一起删除 * ACT_RU_IDENTITYLINK、 * ACT_RU_EVENT_SUBSCR */
@Test
public void delete() { 

try { 

// 部署ID ACT_RE_DEPLOYMENT
String 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.3 查询部署的流程定义数据
/** * 查询部署的流程定义数据 ACT_RE_PROCDEF * 需求:如果有多个相同流程定义标识key的流程时,只查询其最新版本 */
@Test
public void getProcessDefinitionList() { 

// 1. 获取 ProcessDefinitionQuery
ProcessDefinitionQuery 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);
}
3.4 挂起或者激活流程定义
/** * 通过流程定义id,挂起或激活流程定义 */
@Test
public void updateProcessDefinitionState() { 

// 流程定义ID ACT_RE_PROCDEF
String 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);
}
}
3.5 导出流程定义相关文件
/** * 导出下载流程定义相关的文件(.bpmn20.xml流程描述或.png图片资源) * @throws Exception */
@Test
public void exportProcDefFile() throws Exception { 

// 流程定义id ACT_RE_PROCDEF
String 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) 文件成功");
}

四、流程操作–流程实例

4.1 启动流程
/** * 启动流程实例 * ACT_HI_TASKINST 任务实例 * ACT_HI_PROCINST 流程实例 * ACT_HI_ACTINST 节点实例 * ACT_HI_IDENTITYLINK 流程实例相关办理人 * ACT_RU_EXECUTION 运行时流程执行实例表 * ACT_RU_TSK 运行时流程任务实例 * ACT_RU_IDENTITYLINK 运行时流程实例相关办理人 */
@Test
public void startProcessInstance() { 

// 流程定义唯一标识key ACT_RE_PROCDEF 字段KEY_
String processKey = "leave";
//业务id
String 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());
}

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

4.2 查询正在运行的流程实例
/** * 查询正在运行中的流程实例 */
@Test
public 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() ? "已挂起(暂停)" : "已激活(启动)"));
});
}
4.3 挂起或者激活流程实例
/** * 挂起或激活流程实例 */
@Test
public 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.4 删除流程实例
/** * 删除流程实例 * ACT_RU_IDENTITYLINK * ACT_RU_INTEGRATION * ACT_RU_TASK */
@Test
public 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);
}
4.5 查询已结束的流程实例
/** * 查询已结束的流程实例 * * @param req * @return */
@Test
public void getProcInstFinish() { 

HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
.finished() //已结束
.orderByProcessInstanceEndTime()
.desc();
List<HistoricProcessInstance> list =
query.list();
list.stream().forEach(l -> { 

System.out.println(l.getId());//流程实例id
System.out.println(l.getName());//流程名称
System.out.println(l.getProcessDefinitionKey());//流程定义key
System.out.println(l.getProcessDefinitionVersion());//流程定义版本
System.out.println(l.getStartUserId());//流程发起人
System.out.println(l.getBusinessKey());//业务ID
System.out.println(l.getStartTime()));//流程实例开始时间
System.out.println(l.getEndTime()));//流程实例结束时间
System.out.println(l.getDeleteReason());//删除原因
});
}

五、流程操作–流程任务

5.1 查询当前用户代理人或者候选人的任务
/** * 查询当前用户是办理人或候选人的待办任务 pc * * @param req * @return */
@Test
public 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());//任务ID
System.out.println(l.getName());//任务名称
System.out.println(l.isSuspended() );//状态
System.out.println(l.getAssignee());//流程定义版本
System.out.println(l..getAssignee());//办理人
System.out.println(l.getProcessInstanceId());//流程实例ID
System.out.println(l.getCreateTime()));//任务创建时
System.out.println(l.getExecutionId());//执行对象ID
System.out.println(l.getProcessDefinitionId());//流程定义ID
});
5.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;
}
5.3 获取当前审批节点,用来设置下一节点办理人,如果有排他或并行网关,需要获取其集合
/** * 判断当前节点的下一节点是人工任务的集合 * * @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);
}
}
}
5.4 签收任务
/** * 签收(拾取)任务 */
@Test
public 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,就可以取消拾取任务

5.5 任务转交
/** * 任务转交 */
@Test
public 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.6 完成任务
/** * 执行任务 */
@Test
public 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);
}
5.7 设置下一个节点人
/** * 完成当前节点时,设置下一节点任务办理人(上面输入框指定的那个办理人) */
@Test
public 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);
}
}
}
}
5.8 获取已完成任务用于节点跳转
	@Test
public 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;
}
5.9 节点跳转
/** * 1. 取得当前节点信息 * 2. 获取驳回的目标节点信息 * 要考虑并行网关:从选中的目标节点的上级节点(如:并行网关),找到其上级节点的所有子节点,并行 网关就会有多条子节点 * 3. 将当前节点出口指定为驳回的目标节点,(并行网关是多条) * 4. 完成当前节点任务,删除执行表 is_active_=0数据,不然并行汇聚不向后流转;删除其他并行任务, * 5. 分配目标节点原办理人。 * * @param taskId 当前任务id * @param targetActivityId 回滚节点 TaskDefinitionKey (getBackNodes这个方法设置) * @param userId * @return */
@Test
public 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. 获取流程模型实例 BpmnModel
BpmnModel 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);
//----------------------第三部分
//流程实例id
String procInstId = task.getProcessInstanceId();
// 10. 完成当前任务,流程就会流向目标节点创建新目标任务
// 删除已完成任务,删除已完成并行任务的执行数据 act_ru_execution
List<Task> list = taskService.createTaskQuery().processInstanceId(procInstId).list();
list.forEach(t -> { 

// 当前任务id
if (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);
}

六、流程操作–流程历史

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()); //任务id
System.out.println(hti.getName()); //任务名称
System.out.println(hti.getProcessInstanceId()); //流程实例ID
System.out.println(hti.getStartTime()); //开始时间
System.out.println(hti.getEndTime()); //结束时间
System.out.println(hti.getAssignee()); //办理人
}

这里也可以配置业务的businessKey来查询相应的历史任务

6.2 获取审批历史记录图
/** * 获取流程实例审批历史 */
@Test
public 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();
}
}
}
}
6.3 查询指定用户的已处理任务
	@Test
public void findCompleteTask() { 

//用户id
String 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());
});
}
6.4 删除已结束的流程实例
/** * 删除已结束的流程实例 * ACT_HI_DETAIL * ACT_HI_VARINST * ACT_HI_TASKINST * ACT_HI_PROCINST * ACT_HI_ACTINST * ACT_HI_IDENTITYLINK * ACT_HI_COMMENT */
@Test
public 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);
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/185539.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • JavaScript 中如何判断变量是否为数字

    JavaScript 中如何判断变量是否为数字简介JavaScript是一种动态类型语言,这意味着解释器在运行时确定变量的类型。实际上,这也允许我们在相同的代码中使用相同的变量来存储不同类型的数据。如果没有文档和一致性,我们在使用代码时并不总是知道变量的类型。当我们期望一个变量是数字时,对字符串或数组进行操作可能会在代码中导致奇怪的结果。在本文中,我们将会介绍一些判断变量是否为数字的函数。像”10″之类的数字的字符串不应被接受。在JavaScript中,诸如NaN,Infinity和-Infinity之类的特殊值也是数字类型的。根据这些要求,

  • DeepFakes深度造假,AI换脸的技术原理是什么?

    DeepFakes深度造假,AI换脸的技术原理是什么?DeepFakes的出现还意味着我们可以在视频中进行大规模的“换脸”。我们大多数人都曾经把自己的照片上传到网络上,因此,我们大多数人的脸都能够轻易地被替换到一些视频中,成为视频的“主角”,凭空捏造一个人根本没有干过的事。不得不说,这是件非常可怕的事情。

  • ODS设计_ods dw

    ODS设计_ods dw1.数据调研2.确定数据范围需要把上端应用需求与ODS数据范围进行验证,以确保应用所需的数据都已经从业务系统中抽取出来,并且得到了很好的组织,以ER模型表示数据主题关系3.根据数据范围进行进一步的数据分析和主题定义把第一步生成的每个ER图中的实体进行分解,分解的结果仍以ER表示为佳4.定义主题元素定义主题、粒度、维、度量、存储期限a.定义维的概念特性:维…

  • byte与word的区别_女生类型分类

    byte与word的区别_女生类型分类在Visual C++ 6.0中,BYTE与WORD,DWORD本质上都是一种无符号整型,它们在WINDEF.H中被定义,定义如下:typedef unsigned char BYTE;typedef unsigned short WORD;typedef unsigned long DWORD;也就是说BYTE是无符号的char型(char型本质上也是一…

  • C语言为什么被人们称为表达式语言_c语言中’0’是什么意思

    C语言为什么被人们称为表达式语言_c语言中’0’是什么意思今天无意中敲下:#includeintmain(){printf(“~0==%d\n”,~0);}输出结果是~0==-1;为什么呢?我个人的大概理解如下(不保证对错):以下假设为32位系统;0的补码是0x00000000;~0则是:0xFFFFFFFF(~是按位取反,包括不好位,跟“取反”不是一个概念)0xFFFFFFFF的原码是0

  • HUAWEI Mate40Pro解除账号忘记密码ID强制刷机鸿蒙系统激活锁能解开吗

    HUAWEI Mate40Pro解除账号忘记密码ID强制刷机鸿蒙系统激活锁能解开吗华为Mate40pro账号锁过程需要准备一下工具:windwos系统电脑一台(有条件可以准备配置好点的电脑,可以有效提高解锁效率)。 准备Tpye-c数据线一根(一拖三的数据线不行),其他品牌的数据线也可以。 电脑下载todesk远程控制软件,(进行电脑远程救援)。 安装专业USB端口镜像工具。 关注【刷机爱好者】微信公众账号,获取更多帮助!本次教程简要及目录第一步:将用户电脑USB镜像到我的电脑,进行USB1.0模式底层烧录。第二步:底层烧录完成,成功获取临时权限,手机自动进入fas.

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号