SpringCloud基础框架搭建–5 activiti

这是系列搭建springcloud基础框架的文章,内容包括集成shiro、Mysql主从、seata、activiti、drools、hadoop大数据常用组件、keepalive+nginx https配置等;

创建 chen-activiti:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.chen</groupId>
        <artifactId>chen-frame-api</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>com.chen</groupId>
        <artifactId>chen-common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>

    <!-- Activity -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>6.0.0</version>
        <exclusions>
            <exclusion>
                <artifactId>mybatis</artifactId>
                <groupId>org.mybatis</groupId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- Activiti modeler 可视化依赖 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-modeler</artifactId>
        <version>5.23.0</version>
    </dependency>

    <!-- Activity6 集成 Modeler 需要 jar 包 -->
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-transcoder</artifactId>
        <version>1.8</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-codec</artifactId>
        <version>1.8</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-json-converter</artifactId>
        <version>6.0.0</version>
    </dependency>

    <dependency>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
        <version>1.4.01</version>
    </dependency>

    <!-- activiti整合动态流程图所需引入的pom文件-->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-layout</artifactId>
        <version>5.18.0</version> <!-- 5.23.0 报错 com.github.jgraph:jgraphx:pom:v3.9.10 -->
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <executions>
                <execution>
                    <id>repackage</id>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

ActivitiApplication.java

@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class, SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
@EnableFeignClients(value = {"com.chen.frame.api"})
@ComponentScan(basePackages = {"com.chen.frame.common", "com.chen.frame.api", "com.chen.frame.activiti"})
public class ActivitiApplication {

    public static void main(String ...args) {
        //SpringApplication.run(ManagerApplication.class, args);

        new SpringApplicationBuilder(ActivitiApplication.class)
                .properties(new ConcurrentHashMap<String, Object>(){{
                    put("frame.module.basePackages", "com.chen.frame.activiti.mapper");
                }}).run(args);
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer () {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setIgnoreUnresolvablePlaceholders(true);
        return configurer;
    }

    @Bean
    @Primary
    public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
        // 解析器
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        // 注册XSS SQL 解析器
        SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer");
        xssModule.addSerializer(new XssSqlStringJsonSerializer());
        objectMapper.registerModule(xssModule);
        return objectMapper;
    }
}

ProcessEngineConfig ,activiti引擎配置

package com.chen.frame.activiti.configuration;

import com.alibaba.druid.filter.config.ConfigTools;
import lombok.Data;
import org.activiti.engine.*;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * 流程引擎配置文件
 **/
@Configuration
@ConfigurationProperties(prefix = "spring.shardingsphere")
@Data
public class ProcessEngineConfig {
    private Logger logger = LoggerFactory.getLogger(ProcessEngineConfig.class);
    // 从 yml 配置文件中获取 mysql 配置信息
    @Value("${spring.shardingsphere.datasource.master.url}")
    private String url;

    @Value("${spring.shardingsphere.datasource.master.driver-class-name}")
    private String driverClassName;

    @Value("${spring.shardingsphere.datasource.master.username}")
    private String username;

    @Value("${spring.shardingsphere.datasource.master.password}")
    private String password;

    //@Value("${spring.datasource.publicKey}")
    private String publicKey;

    /**
     * 初始化流程引擎
     * @return
     */
    @Primary
    @Bean
    public ProcessEngine initProcessEngine() {
        logger.info("=============================ProcessEngineBegin=============================");

        // 流程引擎配置
        ProcessEngineConfiguration cfg = null;

        try {
            cfg = new StandaloneProcessEngineConfiguration()
                    .setJdbcUrl(url)
                    .setJdbcUsername(username)
                    // 对密码进行解密
                    //.setJdbcPassword(ConfigTools.decrypt(publicKey, password))
                    .setJdbcPassword(password)
                    .setJdbcDriver(driverClassName)
                    // 初始化基础表,不需要的可以改为 DB_SCHEMA_UPDATE_FALSE
                    .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE)
                    // 默认邮箱配置
                    // 发邮件的主机地址,先用 QQ 邮箱
                    .setMailServerHost("smtp.qq.com")
                    // POP3/SMTP服务的授权码
                    .setMailServerPassword("3676383abc")
                    // 默认发件人
                    .setMailServerDefaultFrom("345268267@qq.com")
                    // 设置发件人用户名
                    .setMailServerUsername("管理员");
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 初始化流程引擎对象
        ProcessEngine processEngine = cfg.buildProcessEngine();
        logger.info("=============================ProcessEngineEnd=============================");
        return processEngine;
    }

    //八大接口
    // 业务流程的定义相关服务
    @Bean
    public RepositoryService repositoryService(ProcessEngine processEngine){
        return processEngine.getRepositoryService();
    }

    // 流程对象实例相关服务
    @Bean
    public RuntimeService runtimeService(ProcessEngine processEngine){
        return processEngine.getRuntimeService();
    }

    // 流程任务节点相关服务
    @Bean
    public TaskService taskService(ProcessEngine processEngine){
        return processEngine.getTaskService();
    }

    // 流程历史信息相关服务
    @Bean
    public HistoryService historyService(ProcessEngine processEngine){
        return processEngine.getHistoryService();
    }

    // 表单引擎相关服务
    @Bean
    public FormService formService(ProcessEngine processEngine){
        return processEngine.getFormService();
    }

    // 用户以及组管理相关服务
    @Bean
    public IdentityService identityService(ProcessEngine processEngine){
        return processEngine.getIdentityService();
    }

    // 管理和维护相关服务
    @Bean
    public ManagementService managementService(ProcessEngine processEngine){
        return processEngine.getManagementService();
    }

    // 动态流程服务
    @Bean
    public DynamicBpmnService dynamicBpmnService(ProcessEngine processEngine){
        return processEngine.getDynamicBpmnService();
    }
    //八大接口 end
}

注:可以运行 下项目,让创建 activiti 相关表;
生成后,把上面的配置 “setDatabaseSchemaUpdate” 改为 false ;
bootstrap.yml

server:
  port: 8055

spring:
  application:
    name: chen-activiti
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
        group: DEFAULT_GROUP
        shared-configs[0]:
          data-id: application-dev.yml
          group: DEFAULT_GROUP
          refresh: true
        shared-configs[1]:
          data-id: chen-druid-dev.yml
          group: DEFAULT_GROUP
          refresh: true
  profiles:
    active: dev

  main:
    allow-bean-definition-overriding: true

  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    timeout: 3000
    max-wait: 30000
    max-active: 100
    max-idle: 20
    min-idle: 0

mybatis:
  mapperLocations: classpath:mapper/**Mapper.xml
  type-aliases-package: com.chen.frame.activiti.mapper
  configuration:
    map-underscore-to-camel-case: true

# shardingsphere 数据库安全检测
management:
  health:
    db:
      enabled: false

第一步,集成设计器;
参考:https://blog.csdn.net/qq_37143673/article/details/102667824
资源下载: https://github.com/Activiti/Activiti/releases/tag/5.23.0/
Activiti-5.23.0.tar.gz
解压到当前目录下: tar -xvf Activiti-5.23.0.tar.gz
基础包 com.chen.frame.activiti 下,创建 editor 包;
在 resources 下创建 static 目录 ;
将 /Users/apple/workspace/activiti/Activiti-5.23.0/modules/activiti-webapp-explorer2/src/main/webapp/ 下
diagram-viewer、editor-app、modeler.html 拷到 static 目录下 ;

将 /Users/apple/workspace/activiti/Activiti-5.23.0/modules/activiti-modeler/src/main/java/org/activiti/rest/editor/ 下
main、model两个目录下的三个文件,拷到 com.chen.frame.activiti 基础包的 editor 子包下;
ModelEditorJsonRestResource、ModelSaveRestResource、StencilsetRestResource;

将 /Users/apple/workspace/activiti/Activiti-5.23.0/modules/activiti-webapp-explorer2/src/main/resources/stencilset.json
拷到 chen-activiti/resources 下;
打开 staitc/editor-app/app-cfg.js

ACTIVITI.CONFIG = {
   'contextRoot' : '/activiti-explorer/service',
};

按上面的contextRoot 要在“ModelEditorJsonRestResource、ModelSaveRestResource、StencilsetRestResource” 的类上都添加 requestmapping 注解:

@RestController
@RequestMapping(value = "/activiti-explorer/service")
public class ModelSaveRestResource implements ModelDataJsonConstants

修改相关方法:
修改 “ModelSaveRestResource” 中的 saveModel 方法;

@RequestMapping(value="/model/{modelId}/save", method = RequestMethod.PUT)
@ResponseStatus(value = HttpStatus.OK)
public void saveModel(@PathVariable String modelId, String name, String description, String json_xml, String svg_xml) {
  try {

    Model model = repositoryService.getModel(modelId);

    ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());

    modelJson.put(MODEL_NAME, name);
    modelJson.put(MODEL_DESCRIPTION, description);
    model.setMetaInfo(modelJson.toString());
    model.setName(name);

    repositoryService.saveModel(model);

    repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));

    InputStream svgStream = new ByteArrayInputStream(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);
  }
}

创建 activitiController.java ;

/**
 * 工作流代码示例
 * @author: linjinp
 * @create: 2019-10-22 10:13
 **/
@RestController
@RequestMapping("/activiti-explorer/model")
public class ActivitiController {
    /**
 * 创建基本模型
 * @param request
 * @param response
 */
  @RequestMapping("/create")
  public void createModel(HttpServletRequest request, HttpServletResponse response){
    try{
        String modelName = "modelName";
        String modelKey = "modelKey";
        String description = "description";

        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService repositoryService = processEngine.getRepositoryService();

        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.put("stencilset", stencilSetNode);
        // 定义新模型
        Model modelData = repositoryService.newModel();

        ObjectNode modelObjectNode = objectMapper.createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, modelName);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelData.setMetaInfo(modelObjectNode.toString());
        modelData.setName(modelName);
        modelData.setKey(modelKey);

        //保存模型
        repositoryService.saveModel(modelData);
        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
        response.sendRedirect(request.getContextPath() + "/modeler.html?modelId=" + modelData.getId());
    }catch (Exception e){
    }
  }
}

运行 chen-activiti;
http://localhost:8055/activiti-explorer/model/create
后会转向到 :http://localhost:8055/modeler.html?modelId=1
设计好流程图后,点击保存,创错 avg相关的错误;
-重新 reimport chen-activiti
-再执行 mvn -U idea:idea, 重启 idea
后再执行 chen-activiti 后再保存,提示与 xml 有关的错误 ;
java.lang.ClassNotFoundException: org.w3c.dom.ElementTraversal,添加 maven 依赖

<dependency>
    <groupId>xml-apis</groupId>
    <artifactId>xml-apis</artifactId>
    <version>1.4.01</version>
</dependency

reimport 后保存正常;
==========================================

1.布署模型

@PostMapping(value = "/deployProcess")
public ResultData deployProcess(String modelKey) throws Exception {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();

    Model modelData = repositoryService.createModelQuery().latestVersion()
            .modelKey(modelKey).singleResult();

    byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());

    if (null == bytes) {
        return new ResultData(ResultStatus.FAILURE, "模型不存在");
    }

    JsonNode modelNode = new ObjectMapper().readTree(bytes);
    BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
    model.setTargetNamespace(modelData.getCategory());

    if (model.getProcesses().size() == 0) {
        return new ResultData(ResultStatus.FAILURE,"数据模型不符要求,请至少设计一条主线流程。");
    }
    byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);

    // 发布流程
    String processName = modelData.getName() + ".bpmn20.xml";
    Deployment deployment = repositoryService.createDeployment().name(modelData.getName())
            .category(modelData.getCategory()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();
    modelData.setDeploymentId(deployment.getId());
    repositoryService.saveModel(modelData);

    return new ResultData(ResultStatus.SUCCESS, "模型布暑成功");
}

2. 发起一个流程 :

/**
 * 启动一个实例
 */
@PostMapping(value = "/submitProcess")
public ResultData submitProcess(String defineKey) {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    RuntimeService runtimeService = processEngine.getRuntimeService();

    ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().latestVersion()
            .processDefinitionKey(defineKey).singleResult();

    Map<String, Object> varaibles = new HashMap<>();

    ProcessInstance instance = runtimeService.startProcessInstanceById(pd.getId(), varaibles);

    return new ResultData(ResultStatus.SUCCESS, "", instance.getProcessInstanceId());
}

3. 查询当前用户在流程定义下的待办任务

@PostMapping(value = "/queryTaskByUser")
public ResultData queryTaskByUser(String defineKey, String user) {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取 RepositoryService
    TaskService taskService = processEngine.getTaskService();

    List<Task> taskList = taskService.createTaskQuery()
            .processDefinitionKey(defineKey)
            .includeProcessVariables()
            .taskAssignee(user).list();

    List<TaskVO> taskVOList = new ArrayList<>();
    for(Task task: taskList) {
        TaskVO taskVO = new TaskVO();
        taskVO.setAssignee(task.getAssignee());
        taskVO.setInstanceId(task.getProcessInstanceId());
        taskVO.setTaskId(task.getId());
        taskVO.setTaskName(task.getName());

        taskVOList.add(taskVO);
    }

    return new ResultData(ResultStatus.SUCCESS, "", taskVOList);
}

完成当前待办任务:

@PostMapping(value = "/finishUserTask")
public ResultData finishUserTask(String taskId) {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取 RepositoryService
    TaskService taskService = processEngine.getTaskService();

    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

    if (task != null) {
        taskService.complete(task.getId());
    }

    return new ResultData(ResultStatus.SUCCESS, "ok");
}

绘制流程实例的审批过程图;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping(value = "/draw")
public class ActivitiDrawController {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    //@Autowired
    ProcessEngineConfiguration processEngineConfiguration;

    @GetMapping(value = "/showProcessStatus")
    public void showProcessStatus(HttpServletRequest request, HttpServletResponse response, String instanceId) throws Exception {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        //获取历史流程实例
        HistoricProcessInstance processInstance =  historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
        if (processEngine == null) {
            return;
        }

        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());

        processEngineConfiguration = processEngine.getProcessEngineConfiguration();
        Context.setProcessEngineConfiguration((ProcessEngineConfigurationImpl) processEngineConfiguration);

        ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
        ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());

        List<HistoricActivityInstance> highLightedActivitList =  historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId).list();

        //高亮环节id集合
        List<String> highLightedActivitis = new ArrayList<String>();
        for(HistoricActivityInstance tempActivity : highLightedActivitList){
            String activityId = tempActivity.getActivityId();
            highLightedActivitis.add(activityId);
        }

        //高亮线路id集合
        //List<String> highLightedFlows = getHighLightedFlows(bpmnModel,highLightedActivitList);

        List<String> highLightedFlows =  getHighLightedFlows(bpmnModel,definitionEntity,highLightedActivitList);

        //中文显示的是口口口,设置字体就好了
        InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel,"png",highLightedActivitis,highLightedFlows,"宋体","微软雅黑","黑体",null,2.0);
        //单独返回流程图,不高亮显示
        //InputStream imageStream = diagramGenerator.generatePngDiagram(bpmnModel);
        // 输出资源内容到相应对象
        byte[] b = new byte[1024];
        int len;
        while ((len = imageStream.read(b, 0, 1024)) != -1) {
            response.getOutputStream().write(b, 0, len);
        }

    }
    /**
     * 获取需要高亮的线
     *
     * @param historicActivityInstances
     * @return
     */
    private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 高亮流程已发生流转的线id集合
        List<String> highLightedFlowIds = new ArrayList<>();
        // 全部活动节点
        List<FlowNode> historicActivityNodes = new ArrayList<>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            historicActivityNodes.add(flowNode);
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstances.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode = null;
        FlowNode targetFlowNode = null;
        // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();

            /**
             * 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点,找到匹配流程目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicActivityNodes.contains(targetFlowNode)) {
                        highLightedFlowIds.add(targetFlowNode.getId());
                    }
                }
            } else {
                List<Map<String, Object>> tempMapList = new ArrayList<>();
                for (SequenceFlow sequenceFlow : sequenceFlows) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            Map<String, Object> map = new HashMap<>();
                            map.put("highLightedFlowId", sequenceFlow.getId());
                            map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
                            tempMapList.add(map);
                        }
                    }
                }

                if (!CollectionUtils.isEmpty(tempMapList)) {
                    // 遍历匹配的集合,取得开始时间最早的一个
                    long earliestStamp = 0L;
                    String highLightedFlowId = null;
                    for (Map<String, Object> map : tempMapList) {
                        long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
                        if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
                            highLightedFlowId = map.get("highLightedFlowId").toString();
                            earliestStamp = highLightedFlowStartTime;
                        }
                    }

                    highLightedFlowIds.add(highLightedFlowId);
                }

            }

        }
        return highLightedFlowIds;
    }


    public List<String> getHighLightedFlows(BpmnModel bpmnModel,ProcessDefinitionEntity processDefinitionEntity,List<HistoricActivityInstance> historicActivityInstances)
    {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //24小时制
        List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId

        for (int i = 0; i < historicActivityInstances.size() - 1; i++)
        {
            // 对历史流程节点进行遍历
            // 得到节点定义的详细信息
            FlowNode activityImpl = (FlowNode)bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());


            List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();// 用以保存后续开始时间相同的节点
            FlowNode sameActivityImpl1 = null;

            HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);// 第一个节点
            HistoricActivityInstance activityImp2_ ;

            for(int k = i + 1 ; k <= historicActivityInstances.size() - 1; k++)
            {
                activityImp2_ = historicActivityInstances.get(k);// 后续第1个节点

                if ( activityImpl_.getActivityType().equals("userTask") && activityImp2_.getActivityType().equals("userTask") &&
                        df.format(activityImpl_.getStartTime()).equals(df.format(activityImp2_.getStartTime()))   ) //都是usertask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点
                {

                }
                else
                {
                    sameActivityImpl1 = (FlowNode)bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());//找到紧跟在后面的一个节点
                    break;
                }

            }
            sameStartTimeNodes.add(sameActivityImpl1); // 将后面第一个节点放在时间相同节点的集合里
            for (int j = i + 1; j < historicActivityInstances.size() - 1; j++)
            {
                HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
                HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点

                if (df.format(activityImpl1.getStartTime()).equals(df.format(activityImpl2.getStartTime()))  )
                {// 如果第一个节点和第二个节点开始时间相同保存
                    FlowNode sameActivityImpl2 = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
                    sameStartTimeNodes.add(sameActivityImpl2);
                }
                else
                {// 有不相同跳出循环
                    break;
                }
            }
            List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows() ; // 取出节点的所有出去的线

            for (SequenceFlow pvmTransition : pvmTransitions)
            {// 对所有的线进行遍历
                FlowNode pvmActivityImpl = (FlowNode)bpmnModel.getMainProcess().getFlowElement( pvmTransition.getTargetRef());// 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示
                if (sameStartTimeNodes.contains(pvmActivityImpl)) {
                    highFlows.add(pvmTransition.getId());
                }
            }

        }
        return highFlows;

    }
}

运行:http://localhost:8055/draw/showProcessStatus?instanceId=7501
其他相关参考;
https://blog.csdn.net/weixin_49891230/article/details/124349446 (设计器 )
https://zhuanlan.zhihu.com/p/593527050?utm_id=0 (不错,详细)
https://blog.csdn.net/m0_71777195/article/details/128032767 (详细流程实例及情况)
https://blog.csdn.net/m0_49605579/article/details/125395515 (也不错,最详细)
https://blog.csdn.net/weixin_48351067/article/details/123821155
https://vimsky.com/zh-tw/examples/detail/java-class-org.activiti.engine.repository.ModelQuery.html (查询实例)
https://blog.csdn.net/zhuchunyan_aijia/article/details/101285564 (绘制实例的审批过程图)
https://blog.csdn.net/yabushandaxue/article/details/119297080 (监听器)
https://blog.csdn.net/qq_33333654/article/details/101424876 (监听器,详细,不错)
https://bbs.huaweicloud.com/blogs/302002 (监听器,看得明白)
========================
监听器 :
1)任务监听器
三种类型 :create、assignment、complete (顺序:assignment->create->complete)
实例:

<userTask id="usertask1" name="User Task">
            <extensionElements>
                <activiti:taskListener event="create"
                    class="org.crazyit.activiti.PropertyConfigListener" />
            </extensionElements>
</userTask>

监听器代码:

public class PropertyConfigListener implements TaskListener {

    public void notify(DelegateTask delegateTask) {
        System.out.println("执行任务监听器");
    }
}

表达式形式: (expression)================================

<process id="process1" name="process1">
        <startEvent id="startevent1" name="Start"></startEvent>
        <userTask id="usertask1" name="User Task">
            <extensionElements>
                <activiti:taskListener event="create" expression="${myBean.testBean(task)}"/>
            </extensionElements>
        </userTask>
        <endEvent id="endevent1" name="End"></endEvent>
        <sequenceFlow id="flow1" name="" sourceRef="startevent1"
            targetRef="usertask1"></sequenceFlow>
        <sequenceFlow id="flow2" name="" sourceRef="usertask1"
            targetRef="endevent1"></sequenceFlow>
    </process>

在流程启动时,设置 myBean 参数;需要实现 “Serializable”

public class ExpressionBean implements Serializable {
    public void testBean(DelegateTask task) {
        System.out.println("执行ExpressionBean的 testBean方法: " + task.getId());
    }
}

//=======================
// 创建流程引擎
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 得到流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 得到运行时服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        // 部署流程文件
        repositoryService.createDeployment()
                .addClasspathResource("bpmn/ExpressionTaskListener.bpmn").deploy();
        // 初始化参数
        Map<String, Object> vars = new HashMap<String, Object>();
        vars.put("myBean", new ExpressionBean());
        // 启动流程
        ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);

表达式形式: (delegateExpression)================================

<process id="process1" name="process1" isExecutable="true">
        <startEvent id="startevent1" name="Start"></startEvent>
        <userTask id="usertask1" name="User Task">
            <extensionElements>
                <activiti:taskListener event="create"
                    delegateExpression="${myDelegate}"></activiti:taskListener>
            </extensionElements>
        </userTask>
        <endEvent id="endevent1" name="End"></endEvent>
        <sequenceFlow id="flow1" sourceRef="startevent1"
            targetRef="usertask1"></sequenceFlow>
        <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
    </process>

代码调用, 监听器需要实现 TaskListener, Serializable;

public class DelegateBean implements TaskListener, Serializable {
    public void notify(DelegateTask delegateTask) {
        System.out.println("使用DelegateBean");
    }
}
public class DelegateExpressionTaskListener {
    public static void main(String[] args) {
        // 创建流程引擎
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        // 得到流程存储服务组件
        RepositoryService repositoryService = engine.getRepositoryService();
        // 得到运行时服务组件
        RuntimeService runtimeService = engine.getRuntimeService();
        // 部署流程文件
        repositoryService
                .createDeployment()
                .addClasspathResource(
                        "bpmn/DelegateExpressionTaskListener.bpmn")
                .deploy();
        // 初始化参数
        Map<String, Object> vars = new HashMap<String, Object>();
        vars.put("myDelegate", new DelegateBean());
        // 启动流程
        ProcessInstance pi = runtimeService.startProcessInstanceByKey(
                "process1", vars);
    }
}

任务创建后,会调用myDelegate 的 notify 方法;
==============================================
流程监听器

触发流程监听事件有以下几种:开始事件,结束事件,流程之间的过渡,流程活动的开始和结束,流程网关的开始,中间事件的开始和结束,开始事件的结束和结束事件的开始。只需要将扩展节点放到对应的位置即可。下面示例演示 开始节点,结束节点,第一个流转的监听事件。

bpmn文件

<process id="myProcess" name="My process" isExecutable="true">
    <extensionElements>
        <activiti:executionListener event="end" class="org.scf.act.api.event.listener.EndListener">
            <activiti:field name="message">
                <activiti:string>流程结束</activiti:string>
            </activiti:field>
        </activiti:executionListener>
        <activiti:executionListener event="start" class="org.scf.act.api.event.listener.StartListener">
            <activiti:field name="message">
                <activiti:string>流程开始</activiti:string>
            </activiti:field>
        </activiti:executionListener>
    </extensionElements>
    <startEvent id="startevent1" name="Start"></startEvent>
    <endEvent id="endevent1" name="End"></endEvent>
    <userTask id="usertask1" name="User Task"></userTask>
    <userTask id="usertask2" name="User Task"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1">
        <extensionElements>
            <activiti:executionListener class="org.scf.act.api.event.listener.FlowListener">
                <activiti:field name="message">
                    <activiti:string>开始到task1</activiti:string>
                </activiti:field>
            </activiti:executionListener>
        </extensionElements>
    </sequenceFlow>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
  </process>

以上三个监听类的代码,相同;

public class StartListener implements ExecutionListener{
    
    private FixedValue message;
    
    public FixedValue getMessage() {
        return message;
    }
    public void setMessage(FixedValue message) {
        this.message = message;
    }

    @Override
    public void notify(DelegateExecution execution) {
        System.out.println(message.getExpressionText());
    }

}

运行启动

Deployment deploye = rs.createDeployment().addClasspathResource("event_listener.bpmn").deploy();
    ProcessDefinition  pdf = rs.createProcessDefinitionQuery().deploymentId(deploye.getId()).singleResult();
    
    ProcessInstance  pi = runS.startProcessInstanceById(pdf.getId());
    System.out.println(pi.getId());
    Task task = ts.createTaskQuery().processInstanceId(pi.getId()).singleResult();
    ts.complete(task.getId());
    task = ts.createTaskQuery().processInstanceId(pi.getId()).singleResult();
    ts.complete(task.getId());

运行结果:
流程开始
开始到task1
332505
流程结束

欢迎您的到来,感谢您的支持!

为您推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注