大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新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账号...