• Activiti工作流使用之SpringBoot整合Activiti


    Activiti工作流使用之SpringBoot整合Activiti

    文章目录

    一、springboot整合Activiti环境依赖

    1.1 maven环境
    
    
       4.0.0
    
       com.mengxuegu
       activiti-boot
       1.0-SNAPSHOT
    
    
       
           org.springframework.boot
           spring-boot-starter-parent
           2.5.0
           
       
    
       
           7.1.0.M6
           3.3.1
       
    
       
           
           
               org.activiti
               activiti-spring-boot-starter
               ${activiti.version}
           
           
           
               org.activiti
               activiti-image-generator
               ${activiti.version}
           
           
           
               org.activiti
               activiti-json-converter
               ${activiti.version}
           
           
           
               org.apache.xmlgraphics
               batik-all
               1.10
           
    
           
           
               org.springframework.boot
               spring-boot-starter-web
           
    
           
               mysql
               mysql-connector-java
           
    
           
           
               com.baomidou
               mybatis-plus-boot-starter
               ${mybatis-plus.version}
           
    
           
           
               org.springframework.boot
               spring-boot-starter-security
           
    
           
               org.springframework.boot
               spring-boot-starter-test
               test
           
    
           
               org.projectlombok
               lombok
               1.18.20
           
    
           
               commons-io
               commons-io
               2.6
           
           
       	
           	log4j
           	log4j
           	${log4j.version}
       	
       	
           	org.slf4j
           	slf4j-api
           	${slf4j.version}
       	
       	
           	org.slf4j
           	slf4j-log4j12
           	${slf4j.version}
       	
    		
       
    
       
           
               
                   org.springframework.boot
                   spring-boot-maven-plugin
               
           
       
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    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
    
    # LOGFILE is set to be a File appender using a PatternLayout.
    log4j.appender.LOGFILE=org.apache.log4j.FileAppender
    log4j.appender.LOGFILE.File=f:actactiviti.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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    二、流程操作–流程模型

    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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34

      }

    • 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 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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51

      }

    • 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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      }

    将编辑器静态资源文件放在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();
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

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

    2.3 查询流程模型模板
    /**
        * 查询所有流程定义模板
        */
       @Test
       public void modelList() {
           ModelQuery modelQuery = repositoryService.createModelQuery();
           List 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());
           });
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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("删除成功");
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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();
                   }
               }
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    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 "部署成功";
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    三、流程操作–流程部署

    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("部署成功");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
    • 通过 .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());
               }
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    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 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);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    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);
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    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) 文件成功");
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    四、流程操作–流程实例

    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());
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

    4.2 查询正在运行的流程实例
    /**
        * 查询正在运行中的流程实例
        */
       @Test
       public void getProcInstListRunning() {
    //        //查询正在运行的流程实例 所有
    //        List list = runtimeService.createProcessInstanceQuery().list();
           //查询正在运行的流程实例 按照条件查询
           List 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() ? "已挂起(暂停)" : "已激活(启动)"));
           });
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    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("挂起流程实例");
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    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);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    4.5 查询已结束的流程实例
    /**
        * 查询已结束的流程实例
        *
        * @param req
        * @return
        */
       @Test
       public void getProcInstFinish() {
    
           HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                   .finished() //已结束
                   .orderByProcessInstanceEndTime()
                   .desc();
                   
    		List 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());//删除原因
           });
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    五、流程操作–流程任务

    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 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
    	
           });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    5.2 获得下一个节点任务
    @Tes
    public List> 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> nextNodes = new ArrayList<>();
           getNextNodes(flowElement, nextNodes);
           return nextNodes;
           
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    5.3 获取当前审批节点,用来设置下一节点办理人,如果有排他或并行网关,需要获取其集合
    /**
        * 判断当前节点的下一节点是人工任务的集合
        *
        * @param flowElement 当前节点
        * @param nextNodes   下节点名称集合
        */
       public void getNextNodes(FlowElement flowElement, List> nextNodes) {
           //获取当前节点的连线信息
           List outgoingFlows = ((FlowNode) flowElement).getOutgoingFlows();
    
           for (SequenceFlow outgoingFlow : outgoingFlows) {
               //下一节点
               FlowElement nextFlowElement = outgoingFlow.getTargetFlowElement();
               if (nextFlowElement instanceof EndEvent) {
                   //结束节点
                   break;
               } else if (nextFlowElement instanceof UserTask) {
                   Map 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);
               }
           }
           
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    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 "操作失败";
               }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里取消拾取任务只需要在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);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    5.6 完成任务
    /**
        * 执行任务
        */
       @Test
       public void completeTask() {
           //每个节点就是一个任务, ACT_RU_TASK  ID_
           String taskId = "b10ec157-1c78-11ec-93ec-28d0ea7310b3";
           //执行任务 complete 这个方法还有别的参数, complete(String taskId, Map variables);
           //variables 表示流程变量,可以修改,覆盖之前流程实例初始化时的流程变量
           taskService.complete(taskId);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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 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);
                   }
               }
           }
           
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    5.8 获取已完成任务用于节点跳转
    	@Test
       public List> 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 list =
                   historyService.createNativeHistoricTaskInstanceQuery()
                           .sql(sql)
                           .parameter("processInstanceId", task.getProcessInstanceId())
                           .parameter("taskDefKey", task.getTaskDefinitionKey())
                           .list();
           List> list = new ArrayList<>();
    
           list.forEach(hit -> {
               Map data = new HashMap<>();
               data.put("activityId", hit.getTaskDefinitionKey());
               data.put("activityName", hit.getName());
               list.add(data);
           });
    
           return list;
           
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    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 sequenceFlowList = curFlowNode.getOutgoingFlows();
    
           // 5. 临时存储当前节点的原出口连线
           List oriSequenceFlows = new ArrayList<>();
           oriSequenceFlows.addAll(sequenceFlowList);
    
           // 6. 将当前节点的原出口清空
           sequenceFlowList.clear();
    
           //----------------------第二部分
           // 7. 获取目标节点信息
           FlowNode targetFlowNode = (FlowNode) bpmnModel.getFlowElement(targetActivityId);
           // 8. 获取驳回的新节点
           // 获取目标节点的入口连线
           List incomingFlows = targetFlowNode.getIncomingFlows();
           // 存储获取驳回的新的流向
           List allSequenceFlow = new ArrayList<>();
           for (SequenceFlow incomingFlow : incomingFlows) {
               // 找到入口连线的源头(获取目标节点的父节点)
               FlowNode source = (FlowNode) incomingFlow.getSourceFlowElement();
               // 获取目标节点的父组件的所有出口,
               List 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 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 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);
           
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    六、流程操作–流程历史

    6.1 查询审批历史记录
    @Test
    public void getHistoryInfoList() {
    
    		String procInstId = "ab7567f5-1cfb-11ec-a067-28d0ea7310b3";
           //查询流程人工任务历史数据
           List 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()); //办理人
              
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这里也可以配置业务的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 historicActivityInstances =
                       historyService.createHistoricActivityInstanceQuery()
                               .processInstanceId(procInstId)
                               .orderByHistoricActivityInstanceStartTime().desc()
                               .list();
    
               // 将已经执行的节点id放入高亮显示节点集合
               List highLightedActivityIdList =
                       historicActivityInstances.stream().map(HistoricActivityInstance::getActivityId)
                               .collect(Collectors.toList());
    
               // 通过流程实例ID获取流程中正在执行的节点
               List runningActivityInstanceList =
                       runtimeService.createExecutionQuery().processInstanceId(procInstId).list();
    
               List runningActivityIdList = new ArrayList<>();
               for (Execution execution : runningActivityInstanceList) {
                   if (!StringUtils.isEmpty(execution.getActivityId())) {
                       runningActivityIdList.add(execution.getActivityId());
                   }
               }
    
               // 获取已经流经的流程线,需要高亮显示流程已经发生流转的线id集合
               List 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();
                   }
               }
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    6.3 查询指定用户的已处理任务
    	@Test
       public void findCompleteTask() {
           //用户id
           String userId = "meng";
           List 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());
           });
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    结构型设计模式学习笔记
    1.7-34:回文子串
    【论文阅读】(2023TPAMI)PCRLv2
    [第五空间 2021]WebFTP
    无线渗透-WPA攻击
    【C++】C++多线程库的使用
    flink 操作mongodb的例子
    pikachu靶场-upload-速通
    Sql 导入到 Excel 工具
    Rust迭代器简介
  • 原文地址:https://blog.csdn.net/m0_54849873/article/details/126070579