工作流程引擎之flowable(集成springboot)

news2025/4/16 11:56:03

0、背景

现状:公司各部门业务系统有各自的工作流引擎,也有cross function的业务在不同系统或OA系统流转,没有统一的去规划布局统一的BPM解决方案,近期由于一个项目引发朝着整合统一的BPM方案,特了解一下市面上比较主流的开源和收费的工作流引擎。本文主要介绍开源的工作流引擎flowable.

1、开源工作流引擎比较

开源工作流引擎是一种用于管理和自动化业务流程的软件,它可以帮助用户实现业务流程的可视化设计、流程编排、任务调度、监控和优化等功能。本文将介绍几种常见的开源工作流引擎,并进行比较。目前市场上比较主流的开源流程引擎有:Activiti、Camunda、Flowable。

1.1、Activiti

Activiti是一个轻量级的开源工作流引擎,采用Java语言开发,基于BPMN 2.0规范,支持嵌入式部署和分布式部署。Activiti提供了丰富的API和插件,支持与Java应用程序进行集成。它还提供了Web界面和REST API,可以方便地进行流程设计、部署、调度和监控。Activiti具有以下优点:

  • 易用性和灵活性:Activiti提供了简单易用的流程设计器和API,支持多种流程模型和任务类型,可以满足不同场景和需求的使用。
  • 可伸缩性和性能:Activiti支持嵌入式和分布式部署,可以扩展集群规模以支持更大的业务流程和更高的并发量。此外,它还提供了优化和缓存机制,可以提高性能和响应速度。
  • 社区支持和生态系统:Activiti拥有庞大的社区和活跃的开发者,提供了丰富的插件和工具,可以扩展其功能和使用。例如,Activiti Explorer可以用于流程设计和管理,Camunda Modeler可以用于编辑BPMN模型,Flowable Task可以用于任务管理等。
  • 安全性和可靠性:Activiti提供了可靠的安全性控制,可以对数据进行访问控制和加密,以满足不同场景和需求的安全要求。它还提供了事务管理和错误处理机制,可以保证业务流程的可靠性和稳定性。

1.2、Camunda

Camunda是一个强大的开源工作流引擎,采用Java语言开发,支持BPMN 2.0规范和CMMN规范。Camunda提供了丰富的API和插件,支持与Java应用程序进行集成。它还提供了Web界面和REST API,可以方便地进行流程设计、部署、调度和监控。Camunda具有以下优点:

  • 功能丰富和灵活性:Camunda提供了丰富的功能和灵活的流程设计,支持多种流程模型和任务类型,可以满足不同场景和需求的使用。
  • 可伸缩性和性能:Camunda支持嵌入式和分布式部署,可以扩展集群规模以支持更大的业务流程和更高的并发量。此外,它还提供了优化和缓存机制,可以提高性能和响应速度。
  • 社区支持和生态系统:Camunda拥有庞大的社区和活跃的开发者,提供了丰富的插件和工具,可以扩展其功能和使用。例如,Camunda Modeler可以用于编辑BPMN模型,Camunda Cockpit可以用于任务管理和流程监控,Camunda Tasklist可以用于任务列表等。
  • 可扩展的架构和API:Camunda采用可扩展的架构和API,可以方便地进行集成和扩展。例如,它支持自定义流程引擎插件、外部任务处理器、表单引擎等。
  • 安全性和可靠性:Camunda提供了可靠的安全性控制,可以对数据进行访问控制和加密,以满足不同场景和需求的安全要求。它还提供了事务管理和错误处理机制,可以保证业务流程的可靠性和稳定性。

1.3、Flowable

Flowable是一个开源的轻量级工作流引擎,基于Activiti 5.x版本开发,支持BPMN 2.0规范和CMMN规范。Flowable提供了丰富的API和插件,支持与Java应用程序进行集成。它还提供了Web界面和REST API,可以方便地进行流程设计、部署、调度和监控。Flowable具有以下优点:

  • 易用性和灵活性:Flowable提供了简单易用的流程设计器和API,支持多种流程模型和任务类型,可以满足不同场景和需求的使用。
  • 可伸缩性和性能:Flowable支持嵌入式和分布式部署,可以扩展集群规模以支持更大的业务流程和更高的并发量。此外,它还提供了优化和缓存机制,可以提高性能和响应速度。
  • 社区支持和生态系统:Flowable拥有庞大的社区和活跃的开发者,提供了丰富的插件和工具,可以扩展其功能和使用。例如,Flowable Modeler可以用于流程设计和管理,Flowable Task可以用于任务管理和流程监控,Flowable Admin可以用于集群管理等。
  • 可扩展的架构和API:Flowable采用可扩展的架构和API,可以方便地进行集成和扩展。例如,它支持自定义流程引擎插件、外部任务处理器、表单引擎等。
  • 安全性和可靠性:Flowable提供了可靠的安全性控制,可以对数据进行访问控制和加密,以满足不同场景和需求的安全要求。它还提供了事务管理和错误处理机制,可以保证业务流程的可靠性和稳定性。

2、flowable安装

2.1、下载软件

Github链接,目前flowable已经更新到Flowable 7.0.0.M2版本了。

 安装方式有很多,可以通过下载zip包也可以通过docker拉取镜像进行部署,本文采用zip的window环境部署。

下载安装包,我下载的是flowable-6.7.0版本,同时需要配合tomcat(选择的是apache-tomcat-9.0.79版本)服务进行部署,tomcat下载地址:

 

 下载上面两个软件之后还需要配置flowale的配置数据存储,我选择的mysql数据库,当然他还支持其他数据库类型。mysql数据库是docker镜像运行的,这里不再赘述安装过程,可以看我往期docker mysql安装.

2.2、解压运行

分别解压tomcat和flowable:

 

把flowable-6.7.0\wars目录下的flowable-ui.war拷贝到apache-tomcat-9.0.79\webapps下面启动tomcat  startup.bat

此时会解压war文件,同时会删除war文件,同时会生成新文件apache-tomcat-9.0.79\webapps\flowable-ui.

2.3、配置数据源

 修改apache-tomcat-9.0.79\webapps\flowable-ui\WEB-INF\classes\flowable-default.properties

 参考:

server.port=8080
server.servlet.context-path=/flowable-ui
spring.jmx.unique-names=true
# This is needed to force use of JDK proxies instead of using CGLIB
spring.aop.proxy-target-class=false
spring.aop.auto=false
spring.application.name=flowable-ui
spring.banner.location=classpath:/org/flowable/spring/boot/flowable-banner.txt
# The default domain for generating ObjectNames must be specified. Otherwise when multiple Spring Boot applications start in the same servlet container
# all would be created with the same name (com.zaxxer.hikari:name=dataSource,type=HikariDataSource) for example
spring.jmx.default-domain=${spring.application.name}
#
# SECURITY
#
spring.security.filter.dispatcher-types=REQUEST,FORWARD,ASYNC

# Expose all actuator endpoints to the web
# They are exposed, but only authenticated users can see /info and /health abd users with access-admin can see the others
management.endpoints.web.exposure.include=*
# Full health details should only be displayed when a user is authorized
management.endpoint.health.show-details=when_authorized
# Only users with role access-admin can access full health details
management.endpoint.health.roles=access-admin
# Spring prefixes the roles with ROLE_. However, Flowable does not have that concept yet, so we need to override that with an empty string
flowable.common.app.role-prefix=

#
# SECURITY OAuth2
# Examples are for Keycloak
#
#spring.security.oauth2.resourceserver.jwt.issuer-uri=<keycloakLocation>/auth/realms/<realmName>
#spring.security.oauth2.client.registration.keycloak.client-id=<clientId>
#spring.security.oauth2.client.registration.keycloak.client-secret=<clientSecret>
#spring.security.oauth2.client.registration.keycloak.client-name=Flowable UI Keycloak
#spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
#spring.security.oauth2.client.provider.keycloak.issuer-uri=<keycloakLocation>/auth/realms/<realmName>
#spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

#flowable.common.app.security.type=oauth2
#flowable.common.app.security.oauth2.authorities-attribute=groups
#flowable.common.app.security.oauth2.groups-attribute=userGroups
#flowable.common.app.security.oauth2.default-authorities=access-task
#flowable.common.app.security.oauth2.default-groups=flowableUser
#flowable.common.app.security.oauth2.full-name-attribute=name
#flowable.common.app.security.oauth2.email-attribute=email

#
# DATABASE
#

#spring.datasource.driver-class-name=org.h2.Driver
#spring.datasource.url=jdbc:h2:~/flowable-db/engine-db;AUTO_SERVER=TRUE;AUTO_SERVER_PORT=9093;DB_CLOSE_DELAY=-1

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.56.100:13306/flowable?characterEncoding=UTF-8

#spring.datasource.driver-class-name=org.postgresql.Driver
#spring.datasource.url=jdbc:postgresql://localhost:5432/flowable

#spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
#spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=flowablea

#spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
#spring.datasource.url=jdbc:oracle:thin:@localhost:1521:FLOWABLE

#spring.datasource.driver-class-name=com.ibm.db2.jcc.DB2Driver
#spring.datasource.url=jdbc:db2://localhost:50000/flowable

spring.datasource.username=root
spring.datasource.password=my-secret-pw

# JNDI CONFIG

# If uncommented, the datasource will be looked up using the configured JNDI name.
# This will have preference over any datasource configuration done below that doesn't use JNDI
#
# Eg for JBoss: java:jboss/datasources/flowableDS
#
#spring.datasource.jndi-name==jdbc/flowableDS

# Set whether the lookup occurs in a J2EE container, i.e. if the prefix "java:comp/env/" needs to be added if the JNDI
# name doesn't already contain it. Default is "true".
#datasource.jndi.resourceRef=true

#
# Connection pool (see https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby)
#

spring.datasource.hikari.poolName=${spring.application.name}
# 10 minutes
spring.datasource.hikari.maxLifetime=600000
# 5 minutes
spring.datasource.hikari.idleTimeout=300000
spring.datasource.hikari.minimumIdle=10
spring.datasource.hikari.maximumPoolSize=50
# test query for H2, MySQL, PostgreSQL and Microsoft SQL Server
#spring.datasource.hikari.connection-test-query=select 1
# test query for Oracle
#spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL
# test query for DB2
#spring.datasource.hikari.connection-test-query=SELECT current date FROM sysibm.sysdummy1

#
# Default Task Executor (will be used for @Async)
#
spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=50
spring.task.execution.pool.queue-capacity=10000
spring.task.execution.thread-name-prefix=flowable-ui-task-Executor-

#
# Task scheduling
#
spring.task.scheduling.pool.size=5

#
# EMAIL
#

#flowable.mail.server.host=localhost
#flowable.mail.server.port=1025
#flowable.mail.server.username=
#flowable.mail.server.password=

#
# FLOWABLE
#

flowable.process.definition-cache-limit=512
#flowable.dmn.strict-mode=false
flowable.process.async.executor.default-async-job-acquire-wait-time=PT5S
flowable.process.async.executor.default-timer-job-acquire-wait-time=PT5S

flowable.cmmn.async.executor.default-async-job-acquire-wait-time=PT5S
flowable.cmmn.async.executor.default-timer-job-acquire-wait-time=PT5S

# The maximum file upload limit. Set to -1 to set to 'no limit'. Expressed in bytes
spring.servlet.multipart.max-file-size=10MB
# The maximum request size limit. Set to -1 to set to 'no limit'.
# When multiple files can be uploaded this needs to be more than the 'max-file-size'.
spring.servlet.multipart.max-request-size=10MB

# For development purposes, data folder is created inside the sources ./data folder
flowable.content.storage.root-folder=data/
flowable.content.storage.create-root=true

flowable.common.app.idm-admin.user=admin
flowable.common.app.idm-admin.password=test

flowable.experimental.debugger.enabled=false

# Rest API in task application

# If false, disables the rest api in the task app
flowable.task.app.rest-enabled=true

# Configures the way user credentials are verified when doing a REST API call:
# 'any-user' : the user needs to exist and the password need to match. Any user is allowed to do the call (this is the pre 6.3.0 behavior)
# 'verify-privilege' : the user needs to exist, the password needs to match and the user needs to have the 'rest-api' privilege
# If nothing set, defaults to 'verify-privilege'
flowable.rest.app.authentication-mode=verify-privilege

# Enable form field validation after form submission on the engine side
flowable.form-field-validation-enabled=false

# Flowable Admin Properties

# Passwords for rest endpoints and master configs are stored encrypted in the database using AES/CBC/PKCS5PADDING
# It needs a 128-bit initialization vector (http://en.wikipedia.org/wiki/Initialization_vector)
# and a 128-bit secret key represented as 16 ascii characters below
#
# Do note that if these properties are changed after passwords have been saved, all existing passwords
# will not be able to be decrypted and the password would need to be reset in the UI.
flowable.admin.app.security.encryption.credentials-i-v-spec=j8kdO2hejA9lKmm6
flowable.admin.app.security.encryption.credentials-secret-spec=9FGl73ngxcOoJvmL
#flowable.admin.app.security.preemptive-basic-authentication=true

# Flowable IDM Properties

#
# LDAP
#
#flowable.idm.ldap.enabled=true
#flowable.idm.ldap.server=ldap://localhost
#flowable.idm.ldap.port=10389
#flowable.idm.ldap.user=uid=admin, ou=system
#flowable.idm.ldap.password=secret
#flowable.idm.ldap.base-dn=o=flowable
#flowable.idm.ldap.query.user-by-id=(&(objectClass=inetOrgPerson)(uid={0}))
#flowable.idm.ldap.query.user-by-full-name-like=(&(objectClass=inetOrgPerson)(|({0}=*{1}*)({2}=*{3}*)))
#flowable.idm.ldap.query.all-users=(objectClass=inetOrgPerson)
#flowable.idm.ldap.query.groups-for-user=(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))
#flowable.idm.ldap.query.all-groups=(objectClass=groupOfUniqueNames)
#flowable.idm.ldap.query.group-by-id=(&(objectClass=groupOfUniqueNames)(uniqueId={0}))
#flowable.idm.ldap.attribute.user-id=uid
#flowable.idm.ldap.attribute.first-name=cn
#flowable.idm.ldap.attribute.last-name=sn
#flowable.idm.ldap.attribute.email=mail
#flowable.idm.ldap.attribute.group-id=cn
#flowable.idm.ldap.attribute.group-name=cn
#flowable.idm.ldap.cache.group-size=10000
#flowable.idm.ldap.cache.group-expiration=180000

#
# Keycloak
#
#flowable.idm.app.keycloak.enabled=true
#flowable.idm.app.keycloak.server=<keycloakLocation>
#flowable.idm.app.keycloak.authentication-realm=master
#flowable.idm.app.keycloak.authentication-user=admin
#flowable.idm.app.keycloak.authentication-password=admin
#flowable.idm.app.keycloak.realm=<realm>

#
# DEFAULT ADMINISTRATOR ACCOUNT
#

flowable.idm.app.admin.user-id=admin
flowable.idm.app.admin.password=test
flowable.idm.app.admin.first-name=Test
flowable.idm.app.admin.last-name=Administrator
flowable.idm.app.admin.email=test-admin@example-domain.tld

# Enable and configure JMS
#flowable.task.app.jms-enabled=true
#spring.activemq.broker-url=tcp://localhost:61616

# Enable and configure RabbitMQ
#flowable.task.app.rabbit-enabled=true
#spring.rabbitmq.addresses=localhost:5672
#spring.rabbitmq.username=guest
#spring.rabbitmq.password=guest

# Enable and configure Kafka
#flowable.task.app.kafka-enabled=true
#spring.kafka.bootstrap-servers=localhost:9092

 默认情况下flowable有没有把mysql驱动程序打入到war包里面,需要手动添加对应的驱动。apache-tomcat-9.0.79\webapps\flowable-ui\WEB-INF\lib 我选择的是mysql-connector-java-5.1.45.jar驱动,具体可以从网上下载也可以通过maven方式从中央仓库拉取。

 再次启动tomcat,访问http://127.0.0.1:8080/flowable-ui 初始用户名和密码:admin/test

3、springboot接入flowable

登录flowable之后选择建模器应用程序,添加一个流程,我这边已经添加了一个简单的请假流程。

新入门可以导入我们流程定义,具体文件在看的我资源。重点介绍springboot如何集成flowable,配置后续有机会在单独介绍里程配置说明。

新建springboot项目添加响应的依赖。

 <dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>6.7.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
        <!-- Flowable 内部日志采用 SLF4J -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
    </dependencies>

application.yml配置:

spring:
  datasource:
    url: jdbc:mysql://192.168.56.100:13306/flowable?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: my-secret-pw
# flowable 配置
flowable:
  # 关闭异步,不关闭历史数据的插入就是异步的,会在同一个事物里面,无法回滚
  # 开发可开启会提高些效率,上线需要关闭
  async-executor-activate: false
server:
  port: 18080

Controller

import liquibase.pro.packaged.O;
import liquibase.pro.packaged.U;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
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.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author pyj
 * @date 2019/10/30
 */
@RestController
@RequestMapping("flowable")
public class TestController {

    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ProcessEngine processEngine;

    /**
     * 创建流程
     *
     * @param userId
     * @param days
     * @param reason
     * @return
     */
    @GetMapping("add")
    public String addExpense(String userId, String days, String reason) {
        Map<String, Object> map = new HashMap<>();
        map.put("employee", userId);
        map.put("nrOfHolidays", days);
        map.put("description", reason);

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidayRequest", map);
        return "提交成功,流程ID为:" + processInstance.getId();
    }

    /**
     * 获取指定用户组流程任务列表
     *
     * @return
     */
    @GetMapping("listtask")
    public Object listtask() {
        StringBuffer btf = new StringBuffer();
        List<Task> list = taskService.createTaskQuery().list();
        for (Task task : list){
            btf.append(task.getId()+"\r\n");
            System.out.println(task.getId());
        }
        return btf.toString();
    }

    /**
     * 获取指定用户组流程任务列表
     *
     * @param group
     * @return
     */
    @GetMapping("list")
    public Object list(String group) {
        List<Task> list = taskService.createTaskQuery().list();
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(group).list();
        return tasks.toString();
    }

    /**
     * 通过/拒绝任务
     *
     * @param taskId
     * @param approved 1 :true  2:false
     * @return
     */
    @GetMapping("apply")
    public String apply(String taskId, String approved) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return "流程不存在";
        }
        Map<String, Object> variables = new HashMap<>();
        Boolean apply = approved.equals("1") ? true : false;
        variables.put("approved", apply);
        taskService.complete(taskId, variables);
        return "审批是否通过:" + approved;

    }

    /**
     * 查看历史流程记录
     *
     * @param processInstanceId
     * @return
     */
    @GetMapping("historyList")
    public Object getHistoryList(String processInstanceId) {
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).finished().orderByHistoricActivityInstanceEndTime().asc().list();

        return historicActivityInstances;
    }

    /**
     * 驳回流程实例
     *
     * @param taskId
     * @param targetTaskKey
     * @return
     */
    @GetMapping("rollbask")
    public String rollbaskTask(String taskId, String targetTaskKey) {
        Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (currentTask == null) {
            return "节点不存在";
        }
        List<String> key = new ArrayList<>();
        key.add(currentTask.getTaskDefinitionKey());


        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(currentTask.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(key, targetTaskKey)
                .changeState();
        return "驳回成功...";
    }


    /**
     * 终止流程实例
     *
     * @param processInstanceId
     */
    public String deleteProcessInstanceById(String processInstanceId) {
        // ""这个参数本来可以写删除原因
        runtimeService.deleteProcessInstance(processInstanceId, "");
        return "终止流程实例成功";
    }


    /**
     * 挂起流程实例
     *
     * @param processInstanceId 当前流程实例id
     */
    @GetMapping("hangUp")
    public String handUpProcessInstance(String processInstanceId) {
        runtimeService.suspendProcessInstanceById(processInstanceId);
        return "挂起流程成功...";
    }

    /**
     * 恢复(唤醒)被挂起的流程实例
     *
     * @param processInstanceId 流程实例id
     */
    @GetMapping("recovery")
    public String activateProcessInstance(String processInstanceId) {
        runtimeService.activateProcessInstanceById(processInstanceId);
        return "恢复流程成功...";
    }


    /**
     * 判断传入流程实例在运行中是否存在
     *
     * @param processInstanceId
     * @return
     */
    @GetMapping("isExist/running")
    public Boolean isExistProcIntRunning(String processInstanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            return false;
        }
        return true;
    }

    /**
     * 判断流程实例在历史记录中是否存在
     * @param processInstanceId
     * @return
     */
    @GetMapping("isExist/history")
    public Boolean isExistProcInHistory(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (historicProcessInstance == null) {
            return false;
        }
        return true;
    }


    /**
     * 我发起的流程实例列表
     *
     * @param userId
     * @return 流程实例列表
     */
    @GetMapping("myTasks")
    public List<HistoricProcessInstance> getMyStartProcint(String userId) {
        List<HistoricProcessInstance> list = historyService
                .createHistoricProcessInstanceQuery()
                .startedBy(userId)
                .orderByProcessInstanceStartTime()
                .asc()
                .list();
        return list;
    }


    /**
     * 查询流程图
     *
     * @param httpServletResponse
     * @param processId
     * @throws Exception
     */
    @RequestMapping(value = "processDiagram")
    public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {

        List<ActivityInstance> activityInstanceList =  runtimeService.createActivityInstanceQuery().list();
        for(ActivityInstance activityInstance : activityInstanceList){
            System.out.println(activityInstance.getId());
        }

        System.out.println("=========================================================================");

        List<ProcessInstance> list =  runtimeService.createProcessInstanceQuery().list();
        for(ProcessInstance processInstance : list){
            System.out.println(processInstance.getId());
        }

        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();

        //流程走完的不显示图
        if (pi == null) {
            return;
        }
        Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
        String InstanceId = task.getProcessInstanceId();
        List<Execution> executions = runtimeService
                .createExecutionQuery()
                .processInstanceId(InstanceId)
                .list();

        //得到正在执行的Activity的Id
        List<String> activityIds = new ArrayList<>();
        List<String> flows = new ArrayList<>();
        for (Execution exe : executions) {
            List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
            activityIds.addAll(ids);
        }

        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
        ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0,true);
        OutputStream out = null;
        byte[] buf = new byte[1024];
        int legth = 0;
        try {
            out = httpServletResponse.getOutputStream();
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }

}

Flowable提供了几个Service接口和实现类,可以通过service拿到流程的一些定义、流转等信息。

正如TestController里面定义的几个方法,分别是实例化流程,审批流程,查看流程等操作。如下

实例化流程:

审批流程:

拉取当前流程任务清单:

查看流程状态:

具体大家可以参考如下文章有详细的springboot集成指引。

Flowable BPMN 用户手册 (v 6.3.0)

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/918597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring之Spring Security - 实现身份验证与授权

Spring Security - 实现身份验证与授权 标题: Spring Security - 实现身份验证与授权摘要:引言:词汇解释:详细介绍:实现基本的身份验证与授权解释概念:代码示例:注意事项: 定制化认证与授权流程解释概念:代码示例:注意事项: 集成OAuth2认证解释概念:代码示例:注意事项: 总结:参…

【微信公众号】微信公众号网页授权:

文章目录 一、添加业务域名二、前端授权获取code【1】参数说明&#xff1a;【2】静默授权与显式授权的区别&#xff1a;【3】前端获取code的代码&#xff1a; 三、后端获取openid(或unionid)【1】获取openid&#xff1a;【2】获取unionid 四、unionid与openid的区别 在微信公众…

宠物店小程序制作

随着移动互联网的快速发展&#xff0c;宠物店经营者们也纷纷意识到线上市场的潜力。通过开发一款宠物小程序&#xff0c;你可以将你的宠物店推向线上的新机遇&#xff01; 【乔拓云】是一个专业的小程序开发平台&#xff0c;它提供了一套简单易用的工具&#xff0c;帮助你快速创…

JavaSE 方法

定义&#xff1a; 程序执行的最小单元 什么时候用到方法&#xff1a;需要重复运行一段代码的时候&#xff1b; 使用方法的好处&#xff1a;提高代码的可重用性、可维护性 格式&#xff1a; // 一、最简单格式 public static void 方法名(){// 方法体 }// 二、带参数的格式…

炫我为北京轻工技师学院提供渲染私有云系统解决方案

北京轻工技师学院作始建于1964年&#xff0c;是国家级重点学校。学院开设有计算机动画制作、计算机网络应用、电气自动化设备安装与维修、电子技术应用、工业机器人应用与维护等16个专业&#xff0c;本次项目的交付实施涉及该学院的一个重要项目。 ▲北京轻工技师学院 图源网…

【SpringSecurity】五、UserDetails接口和UserDetailsService接口

文章目录 1、SpringSecurity原理2、UserDetails接口3、UserDetailService接口4、权限配置 1、SpringSecurity原理 Spring Security是做安全访问控制&#xff0c;对所有进入系统的请求进行拦截&#xff0c;并做校验&#xff0c;这可以通过Filter或者AOP实现&#xff0c;Spring …

MySQL 条件查询 Emoji 表情符号却返回多条数据【包含其它表情符号】的问题解决 - COLLATION 字符序的选择

1、问题出现 在APP客户端输入搜索文章的关键字时&#xff0c;不小心输入来了一个 emoji 表情符号&#xff0c;提示出错了&#xff0c;在后台查询错误日志信息&#xff0c;提示查询出现了2条相同的记录&#xff1a; Caused by: org.hibernate.NonUniqueResultException: query …

【Java 高阶】一文精通 Spring MVC - 数据格式化器(六)

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

python - 编程中【工厂模式】和【单例模式】区别以及代码示例详解

一. 概念 工厂模式和单例模式都是面向对象编程中常用的设计模式。 工厂模式&#xff08;FactoryPattern&#xff09;&#xff1a;是一种创建型模式&#xff0c;它提供了一种方法来创建对象&#xff0c;而不需要暴露对象的创建逻辑。这种模式通过定义一个工厂类&#xff0c;通…

优化生产流程:数字化工厂中的OPC UA分布式IO模块应用

背景 近年来&#xff0c;为了提升在全球范围内的竞争力&#xff0c;制造企业希望自己工厂的机器之间协同性更强&#xff0c;自动化设备采集到的数据能够发挥更大的价值&#xff0c;越来越多的传统型工业制造企业开始加入数字化工厂建设的行列&#xff0c;实现智能制造。 数字化…

归一化的作用,sklearn 安装

目录 归一化的作用&#xff1a; 应用场景说明 sklearn 准备工作 sklearn 安装 sklearn 上手 线性回归实战 归一化的作用&#xff1a; 归一化后加快了梯度下降求最优解的速度; 归一化有可能提高精度(如KNN) 应用场景说明 1&#xff09;概率模型不需要归一化&#xff…

强化历程5-Java并发系列(2023.8.23)

文章目录 强化历程5-Java并发系列(2023.8.23)1 Java多线程1.1 Java中多线程有几种实现方式&#xff1f;1.2 那么Runnable和Callable都可以实现多线程&#xff0c;他们有什么区别?1.3 采用实现Runnable和Callable接口方式和采用继承Thread类方式各有什么好处?1.4 Java如何停止…

【数据结构与算法】—— 手撕红黑树

目录 &#xff08;一&#xff09;红黑树的定义 1、红黑树的引入 2、红黑树的概念 3、红黑树的性质 &#xff08;二&#xff09;红黑树的操作 1、红黑树节点的定义 2、红黑树的插入操作 1️⃣ 思路 2️⃣ 代码实现 3、红黑树的删除操作&#xff08;了解&#xff09; …

Compose - 基本使用

一、概念 1.1 Compose优势 由一个个可以组合的Composable函数拼成界面&#xff0c;方便维护和复用。布局模型不允许多次测量&#xff0c;提升了性能。Compose可以和View互操作&#xff08;相互包含对方&#xff09;。 1.2 声明式UI APP展示的数据绝大多数不是静态数据而是会…

An easy problem

一、题目 we define f(A) 1, f(a) -1, f(B) 2, f(b) -2, … f(Z) 26, f(z) -26; Give you a letter x and a number y , you should output the result of yf(x). Input On the first line, contains a number T.then T lines follow, each line is a case.each case …

多线程基础篇(包教包会)

文章目录 一、第一个多线程程序1.Jconsole观察线程2.线程休眠-sleep 二、创建线程三、Thread类及常见方法1. Thread 的常见构造方法2. Thread 的几个常见属性3. 启动线程 - start4. 中断线程5. 等待一个线程 四、线程状态五、线程安全问题(synchronized)&#xff08;重点&#…

基于React实现日历组件详细教程

前言 日历组件是常见的日期时间相关的组件&#xff0c;围绕日历组件设计师做出过各种尝试&#xff0c;展示的形式也是五花八门。但是对于前端开发者来讲&#xff0c;主要我们能够掌握核心思路&#xff0c;不管多么奇葩的设计我们都能够把它做出来。 本文将详细分析如何渲染一…

windows系统activemq一键安装启动

前言 官网下载的mq提供了启动和卸载服务器的脚本&#xff0c;但是安装的时候不会自动启动服务&#xff0c;需要去手动比较麻烦&#xff0c;执行脚本的时候也需要去右键选择管理员执行做起来比较啰嗦。 优化方案 修改脚本权限为自动判断并获取 在脚本开头加入下面的代码 …

店铺收款系统开源_商城收款分账系统_OctShop

近来&#xff0c;很多客户对OctShop店铺收款分账系统有很大的需求&#xff0c;下面详细介绍一下&#xff0c;门店收款分账系统是什么&#xff0c;以及其作用与意义是什么&#xff1f; 店铺收款分账系统实质上是一个解决门店各种收款场景&#xff0c;如&#xff1a;扫码付款&…

零基础学习,初学者也能轻松制作高质量母婴行业小程序

现如今&#xff0c;随着移动互联网的发展&#xff0c;小程序成为了各行各业的新宠。对于母婴行业来说&#xff0c;拥有一个功能完善、用户友好的小程序&#xff0c;无疑是提升企业形象和服务质量的重要一环。然而&#xff0c;对于初学者来说&#xff0c;如何轻松掌握母婴行业小…