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

2023-10-27

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)

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

工作流程引擎之flowable(集成springboot) 的相关文章

  • 【Java】java | springboot集成flowable报错解决 | 从若依迁移flowable报错 

    一 先说解决方案 开启表自动创建 flowable相关表 flowable true 会对数据库中所有表进行更新操作 如果表不存在 xff0c 则自动创建 建议开发时使用 database schema update true 关闭定时任务
  • docker 挂载 no such file or directory问题

    现象 docker run d p8080 8080 name flowable v data flowable opt tomcat flowable all in one 就报 Error response from daemon OC
  • 工作流程引擎之flowable(集成springboot)

    0 背景 现状 公司各部门业务系统有各自的工作流引擎 也有cross function的业务在不同系统或OA系统流转 没有统一的去规划布局统一的BPM解决方案 近期由于一个项目引发朝着整合统一的BPM方案 特了解一下市面上比较主流的开源和收
  • Flowable 之事件和网关

    文章目录 一 网关 1 1 排他网关 1 2 并行网关 1 3 包容网关 1 4 事件网关 二 事件 2 1 定时器事件 2 1 1 定时器启动事件 2 1 2 中间计时器捕获事件 2 1 3 边界计时器事件 2 2 消息事件 2 2 1
  • flowable(五) - 使用flowable-admin-ui 流程部署到数据库

    前言 看完别人集成boot项目的demo 他的部署方式是通过和moderer项目里面的代码进行部署的 flowable流程资源部署方式 这里提到了Model方式部署 设计器中的发布就是用的此方法这里就是上篇作者的代码 所以说 设计器的发布
  • Waiting for changelog lock....

    Could not acquire change log lock Currently locked by XXXXXX 在本地启动 的时候 一直停在Waiting for changelog lock 然后最后的异常是Could not
  • 《Flowable基础二 Flowable是什么》

    2 1 Flowable是什么 Flowable是一个使用Java编写的轻量级业务流程引擎 Flowable流程引擎让你可以部署BPMN 2 0流程定义 用于定义流程的行业XML标准 创建这些流程定义的流程实例 进行查询 访问运行中或历史的
  • MybatisPlus整合Flowable出现的坑

    MybatisPlus整合Flowable出现的坑 摘要 现在在项目中使用的MybatisPlus 最近研究了一下流程框架Flowable 看了很多技术文档博客 打算直接整合进去 先记录一下遇到的问题 问题 Description file
  • Flowable基本使用介绍和Flowable数据库表解释

    1 Flowable是什么 Flowable是一个使用Java编写的轻量级业务流程引擎 Flowable流程引擎可用于部署BPMN 2 0流程定义 用于定义流程的行业XML标准 创建这些流程定义的流程实例 进行查询 访问运行中或历史的流程实
  • flowable流程实例笔记(1)

    RuntimeService 运行服务类 支持启动的方式 流程定义 从这里获取资源文件 执行实例 流程实例中执行的每个环节 流程实例 一个流程实例包括所有运行的节点 一个流程中流程实例只有一个 启动一个实例 public void star
  • flowable(四) - 使用flowable-modeler-ui 定义流程

    步骤 地址 Flowable Modeler http localhost 8080 flowable modeler Flowable Task http localhost 8080 flowable task Flowable Adm
  • Flowable 用户问题

    Flowable用户和系统用户问题 springboot集成flowable modeler 实现免登 权限管理 Flowable引擎使用统一权限管理
  • flowable 多数据源

    目录 前言 一 多数据源 二 测试 1 测试接口 1 不带事务 2 加上事务 三 解决方法 1 开启新事物 2 重写事务 总结 前言 在springboot中使用flowable 此时flowable默认使用spring中的数据源 我这里f
  • flowable(九) 通过ui部署流程图

    下载xml模型 登陆到flowable modeler后台 上传部署 登陆flowable admin地址 ok
  • Could not acquire change log lock. Currently locked by XXXXXX

    最近公司项目使用到了flowable 为了方便业务开发人员使用流程设计器画流程图 使用了flowable的原生流程设计器modeler 用docker部署非常方便 参考flowable流程设计器部署官网文档 用docker部署flowabl
  • Flowable BPMN 用户手册 (v 6.3.0)

    小编本来想学习 activity但是被同事强烈推荐学习Flowable 百度了一下发现Flowable是activity的一个升级 第一步找学习资料 找到一个分享给大家 https tkjohn github io flowable use
  • flowable实战(四):构建命令行程序

    实际上前面那些都是前置知识 真正上手的话其实还是一脸懵的情况 这里我也是按照文档生成一个最简单的demo 这个demo就是一个简单的Maven工程 你只要启动main方法就可以看到效果 github实例代码地址 这里如果直接下载代码 如果不
  • Flowable入门系列文章29 - Activity解读 05

    1 消息开始事件 描述 甲消息开始事件可用于使用已命名的信息来启动一个过程实例 这有效地允许我们使用消息名称从一组替代开始事件中选择正确的开始事件 在部署具有一个或多个消息启动事件的流程定义时 应考虑以下注意事项 消息开始事件的名称在给定的
  • Flowable入门系列文章113 - 进程实例 02

    1 激活或暂停流程实例 PUT运行时 process instances processInstanceId 表1 激活或暂停流程实例 URL参数 参数 需要 值 描述 processInstanceId 是 串 激活 挂起的流程实例的ID
  • JDK21和 Flowable 7.0.0

    JDK21和 Flowable 7 0 0 一 Flowable 二 项目搭建 1 依赖包 2 数据库 3 资源文件 1 YML配置文件 2 Drools kbase

随机推荐

  • 基于有道API的命令行词典(golang版)

    Godict 本项目地址 近期一直再使用golang语言开发一些工具 相关的后端技术链 golang orm postgresql gin jwt logrus 和对应前端的技术链 vue iview axios vue router 基本
  • matlab逆变器原理,MATLAB中的单相全桥逆变器电路建模与仿真

    电子技术设计和应用电子设计和应用电子技术O 3969 j issn 1000 0755 201 5 03 020 MATLAB中的单相全桥逆变器电路建模与仿真杨露容军刘凯周雷李仁贵 湖南工学院信息与通信工程学院 湖南岳阳 描述了全桥逆变器电
  • 什么是MapReduce,MapReduce的工作流程和原理是什么

    一 MapReduce的概念 MapReduce是一种编程模型 用于大规模数据集 大于1TB 的并行运算 概念 Map 映射 和 Reduce 归约 和它们的主要思想 都是从函数式编程语言里借来的 还有从矢量编程语言里借来的特性 它极大地方
  • 对拦截器的小小理解

    对于初学架构的 color red 小白 color 来讲 拦截器绝对是一把需要掌握的 color red 利器 color 那么自己从以下几个方面 谈谈对拦截器的小小思考 拦截器的方法在Action执行前或执行后自动执行 从而将通用的操作
  • 吐血解决磁盘占用率100%

    吐血解决磁盘占用率100 问题简述 解决步骤 吐血解决 磁盘利用率高的建议 问题简述 一次偶然使用电脑后 发现每次开机后 磁盘长时间占用率达到100 带来的影响是打开浏览器 打开本地电脑磁盘特别卡 解决步骤 1 尝试了网络上提供的绝大部分方
  • 常用的范数求导

    矢量范数的偏导数 L1范数不可微 但是存在次梯度 即是次微分的 L1范数的次梯度如下 x x 1 sign x begin equation begin aligned frac partial partial mathbf x mathb
  • 【CV with Pytorch】第 8 章 :图像超分辨率

    随着高分辨率图像捕获代理的出现 图像中捕获的信息是巨大的 技术已经从超高清转向 4K 和 8K 分辨率 如今 电影正在使用高分辨率帧 但是 在某些情况下 他们需要将低分辨率图像增强为高分辨率图像 想象这样一个场景 电影的主角正试图确定从一张
  • 第一站:探索JavaWeb的神秘世界

    欢迎来到 JavaWeb的奇妙冒险 教学系列 在这里 我们将探索Web开发的奥秘 让你在学习的过程中不仅轻松愉快 还能掌握高质量的知识 JavaWeb的奇妙冒险 第一站 探索JavaWeb的神秘世界 1 什么是JavaWeb 2 为什么学习
  • 2022-03-03JAVA面试笔试题记录

    最近在学习JAVA技术基础 也尝试练习一些JAVA面试中的编程题来巩固相关的知识点 具体的问题和代码如下 问题1 package com interview demo 利用条件运算符的嵌套来完成此题 学习成绩 gt 90分的同学用A表示 6
  • 决策树(Decision Tree,DT)(ID3、C4.5、剪枝、CART)

    目录 1 算法简介 2 特征选择 3 生成决策树 ID3 C4 5 4 修剪决策树 5 CART算法 CART回归树的生成 CART分类树的生成 CART剪枝 1 算法简介 决策树模型是树形结构 既可以用于分类 也可以用于回归 一颗决策树由
  • Mysql数据库基础(四)—— 表的字段类型(Mysql数据类型)

    Mysql的数据类型是一种约束 为了确保数据插入和存储的一致性 一旦我们插入的数据不合法 比如插入与字段类型不符的数据 Mysql会直接终止 这一点上和C语言就有区别 C语言存在隐式类型转换 即便类型不一致 编译器也不会报错 除此之外 虽然
  • 语义分割模型LinkNet介绍

    语义分割模型LinkNet 模型提出的背景 网络结构 实验结果 结论 GitHub代码链接 LinkNet是2017年CVPR上的一篇论文 论文地址 https arxiv org abs 1707 03718 由于网上的论文笔记以及讲解不
  • 推荐收藏

    本文将对数据竞赛的 技巧 进行全面的总结 同时还会分享下个人对比赛方法论的思考 前者比较客观 总结了不同数据类型下涉及到的比赛技巧 后者稍微主观 是我个人对解决比赛思路的总结 2019年下半年对我触动很大的两个知识分享是 志峰现场讲解的 T
  • VLAN是什么,我们为什么需要它?

    VLAN是一组逻辑上的设备和用户 这些设备和用户并不受物理位置的限制 可以根据功能 部门及应用等因素将它们组织起来 相互之间的通信 任何典型的LAN环境都包括各种各样的设备和计算机系统 它们都有各自的用途 有些设备应用是特有的 语音 数据
  • ABAP 新语法记录(一)

    主要内容 内联声明 构造表达式 内表操作 Open SQL 其他 本文列出了ABAP新语法的一些使用方式 供大家学习参考 内联声明 代码实现 pre amp 主题一 内联声明 语法 DATA FILED SYMBOL 1 定义变量 2 定义
  • Ribbon负载均衡(二)Ribbon负载均衡策略

    Ribbon负载均衡策略 文章目录 Ribbon负载均衡策略 1 默认 轮询策略 1 1 修改User服务 使用Ribbon默认轮询策略 1 2 RestTemplate配置 1 3 RestTemplate访问Order订单服务 1 4
  • 数据库多表合为一表

    insert into total select from c1 注 total为总表 c1为单表之一 简单说就是 需要哪些表就把相应的表插入到一个总表里面 注意总表和单表的类型必须一样 select from c1 把表c1的数据全部查出
  • 微信小程序笔记

    空待整理
  • 2.5万字讲解DDD领域驱动设计,从理论到实践掌握DDD分层架构设计,赶紧收藏起来吧

    推荐好文 2 5万字详解23种设计模式 微服务springcloud环境下基于Netty搭建websocket集群实现服务器消息推送 netty是yyds 代码中如何干掉太多的if else即if else的多种替代方案以提高代码质量通过公
  • 工作流程引擎之flowable(集成springboot)

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