当前位置: 首页 > news >正文

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

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=-1spring.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/flowablespring.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=PT5Sflowable.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=trueflowable.common.app.idm-admin.user=admin
flowable.common.app.idm-admin.password=testflowable.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%2B8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: 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 {@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;@Autowiredprivate HistoryService historyService;@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate 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的IdList<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)

 

 

相关文章:

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

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

leetcode54. 螺旋矩阵(java)

螺旋矩阵 题目描述解题 收缩法 上期经典算法 题目描述 难度 - 中等 原题链接 - leecode 54 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7…...

go gorm 查询

定义model package mysqltestimport ("errors""fmt""gorm.io/gorm" )type Product struct {gorm.ModelID uint gorm:"primarykey"Name string gorm:"column:name"Price float64 gorm:"column:price_value&quo…...

Flutter GetXController 动态Tabbar 报错问题

场景&#xff1a; 1.Tabbar的内容是接口获取的 2. TabController? tabController;&#xff1b; 在onInit 方法中初始化tabbarController tabController TabController(initialIndex: 0, length: titleDataList.length, vsync: this); 这时候会报一个错误 Controllers l…...

Redis(缓存预热,缓存雪崩,缓存击穿,缓存穿透)

目录 一、缓存预热 二、缓存雪崩 三、缓存击穿 四、缓存穿透 一、缓存预热 开过车的都知道&#xff0c;冬天的时候启动我们的小汽车之后不要直接驾驶&#xff0c;先让车子发动机预热一段时间再启动。缓存预热是一样的道理。 缓存预热就是系统启动前&#xff0c;提前将相关的…...

UE4/5Niagara粒子特效学习(使用UE5.1,适合新手)

目录 创建空模板 创建粒子 粒子的基础属性 粒子的生命周期 颜色 大小设置 生成的位置 Skeletal Mesh Location的效果&#xff1a; Shape Location 添加速度 添加Noise力场 在生成中添加&#xff1a; 效果&#xff1a; ​编辑 在更新中添加&#xff1a; 效果&…...

from moduleA import * 语句 和import moduleA 的区别

from moduleA import * 语句和import moduleA 的区别是&#xff1a; from moduleA import * 语句会将moduleA模块中的所有内容&#xff08;函数、变量、类等&#xff09;直接导入到当前模块的命名空间中&#xff0c;这样就可以直接使用它们&#xff0c;而不需要加上模块名的限…...

【leetcode 力扣刷题】交换链表中的节点

24. 两两交换链表中的节点 24. 两两交换链表中的节点两两节点分组&#xff0c;反转两个节点连接递归求解 24. 两两交换链表中的节点 题目链接&#xff1a;24. 两两交换链表中的节点 题目内容&#xff1a; 题目中强调不能修改节点内部值&#xff0c;是因为如果不加这个限制的话…...

学会Mybatis框架:让你的代码更具灵活性、可维护性、安全性和高效性【二.动态SQL】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Mybatis的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Mybatis动态SQL如何应用 1.需求 2.…...

Oracle 中 ROWNUM 使用问题记录

ROWNUM 使用问题记录(2023-08-17) Oracle 版本&#xff1a; 19.0.0.0.0 Enterprise现象&#xff1a;今天在项目遇到一个问题&#xff0c;测试人员反馈前一天能看到的数据今天看不到了 用表格举例&#xff0c;这是前一天看到的数据&#xff0c;有9、7、1 这几个数量信息 日期…...

MySQL数据库:内置函数

日期函数 规定&#xff1a;日期&#xff1a;年月日 时间&#xff1a;时分秒 函数名称作用描述current_date()当前日期current_time()当前时间current_timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date_add(date,interval d_value_type)在date中添加…...

【C++杂货铺】探索string的底层实现

文章目录 一、成员变量二、成员函数2.1 默认构造函数2.2 拷贝构造函数2.3 operator2.4 c_str()2.5 size()2.6 operator[ ]2.7 iterator2.8 reserve2.9 resize2.10 push_back2.11 append2.12 operator2.13 insert2.14 erase2.15 find2.16 substr2.17 operator<<2.18 opera…...

c++ day1

定义一个命名空间Myspace&#xff0c;包含以下函数&#xff1a;将一个字符串中的所有单词进行反转&#xff0c;并输出反转后的结果。例如&#xff0c;输入字符串为"Hello World"&#xff0c;输出结果为"olleH dlroW"&#xff0c;并在主函数内测试该函数。 …...

变动的Python爬虫实现

在电商时代&#xff0c;了解商品价格的变动对于购物者和卖家来说都非常重要。本文将分享一种基于Python的实时监控电商平台商品价格变动的爬虫实现方法。通过本文的解决方案和代码示例&#xff0c;您将能够轻松监控商品价格&#xff0c;并及时做出决策。 一、了解需求和目标 在…...

mybatis-plus--配置-(sql)日志输出-自动填充-分页-多数据源-逻辑删除

写在前面&#xff1a; 本文主要介绍mybatis-plus的配置&#xff0c;以后在有的时候在补充。欢迎交流。 文章目录 日志输出自动填充分页全局字段配置多数据源 日志输出 调试的时候需要看执行的sql&#xff0c;这时候就很需要日志来记录查看了。 mybatis-plus的日志配置在yml…...

数据API服务管理功能:解放数据潜力,提升业务效率

数据API服务的重要性 在数字化时代&#xff0c;数据被认为是企业的重要资产。数据API服务的管理功能能够有效帮助企业实现数据的整合和利用。通过合理的数据API服务管理&#xff0c;企业可以更好地解放数据潜力&#xff0c;提升业务效率。 ​ 解放数据潜力 数据API服务管理功…...

云南森林火灾vr消防模拟安全演练系统训练消防员火灾和事故的适应和应对能力

据统计,每一场破坏性地震发生后,会引发次生的灾害,而火灾是其中之一。导致火灾的原因,推测是地震时使供电线路短路,引燃易燃物,火灾就随即发生。所以,在日常生活中,定期的消防演练还是非常必要的, VR消防&#xff0c;是VR公司深圳华锐视点利用VR虚拟现实技术&#xff0c;将VR和…...

(6)(6.2) 任务命令

文章目录 前言 6.2.1 概述 6.2.2 导航命令 6.2.3 条件命令 6.2.4 DO命令 前言 本文介绍了 Copter、Plane 和 Rover 切换到自动模式时支持的任务指令。 &#xff01;Warning 这是一项正在进行中的工作&#xff0c;尚未经过全面审核。有关 Copter 的更佳列表&#xff0c;请…...

【consul】

consul 一、什么是服务注册与发现1.11.2 二、 什么是consul2.1定义2.2特性2.2.1服务注册与发现&#xff1a;2.2.2健康检查&#xff1a;2.2.3Key/Value存储&#xff1a; 三、consul部署-datacenter &#xff1a;指定数据中心名称&#xff0c;默认是dc1。consul &#xff1a;指定…...

Electron环境搭建

Electron是一个优秀的开源框架&#xff0c;用于构建跨平台的桌面应用程序。它基于Chromium和Node.js&#xff0c;使得开发者可以使用Web技术&#xff08;HTML、CSS和JavaScript&#xff09;来构建可在Windows、macOS和Linux等多个操作系统上运行的应用程序。本文将介绍如何搭建…...

MinIO线上扩容实战

硬件投入肯定是随着业务的增长而增长&#xff0c;这就要求中间件平台必须提供水平伸缩机制&#xff0c;MinIO对象存储服务也不例外&#xff0c;本文就详细介绍MinIO的扩容。 Minio支持通过增加新的Server Pool来扩容老的集群。每个Server Pool都是一个相对独立的故障域&#x…...

【微服务】微服务的概论

微服务&#xff1a;构建面向为了解决这个问题&#xff0c;微服务架构应运而生。本文将向您介绍微服务的概念、优势、实现原理以及应用场景&#xff0c;带您领略微服务在构建面向未来的高效应用中的魅力。 一、微服务的概念和优势 微服务是一种将应用拆分为一系列小型、独立服…...

基于Jenkins自动打包并部署docker环境

目录 1、安装docker-ce 2、阿里云镜像加速器 3、构建tomcat 基础镜像 4、构建一个Maven项目 实验环境 操作系统 IP地址 主机名 角色 CentOS7.5 192.168.200.111 git git服务器 CentOS7.5 192.168.200.112 Jenkins git客户端 jenkins服务器 CentOS7.5 192.168…...

jvm 运行时数据区

Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁 1.1程序计数器 程序计数器也叫pc寄存器 可以看作是当前线程…...

Jobs Portal求职招聘系统源码v3.5版本

Jobs Portal求职招聘系统 是为求职者和公司发布职位而开发的交互式求职招聘源码。它使求职者能够发布简历、搜索工作、查看个人工作列表。 它将提供各种公司在网站上放置他们的职位空缺资料&#xff0c;并且还可以选择搜索候选人简历。 除此之外&#xff0c;还有一个管理模块供…...

Android kotlin系列讲解(入门篇)使用Intent在Activity之间穿梭

<<返回总目录 上一篇:Android kotlin系列讲解(入门篇)Activity的理解与基本用法 文章目录 1、使用显式Intent2、使用隐式Intent3、更多隐式Intent的用法4、向下一个Activity传递数据5、返回数据给上一个Activity1、使用显式Intent 你应该已经对创建Activity的流程比较…...

音频编码类型及对应的封装文件

音频编码类型及对应的封装文件 如下表格 编码类型解释文件封装audio/mp4a-latmMPEG-4 Audio Advanced Audio Coding (AAC) Low-Overhead Audio Transport Multiplex (LATM) 压缩的音频格式mp4audio/3gpp3rd Generation Partnership Project (3GPP) 定义的音频编码格式3GPaudi…...

初探科研 | 第一次科研经历

1 . 自己的experiences 自己大二下学期中比较幸运加入到科研组里&#xff0c;做的方向是3D人体姿态估计&#xff0c;不过由于是一个全新领域&#xff0c;基本也是自己这个小白探索&#xff0c;所以成果甚微。在八月初由于各种原因退出了组&#xff0c;但是在这期间收获还是蛮多…...

Wireshark数据抓包分析之HTTP协议

一、实验目的&#xff1a; 主要时熟悉wireshark的使用 二、预备知识&#xff1a; HTTP协议的相关知识 what fk&#xff0c;原来只要在右页点击切换&#xff0c;就可以开启2台不同的机器欸&#xff01;nice 三、实验过程&#xff1a; 1.在机器1中通过管理员身份运行hfs之后&a…...

研发管理工具大揭秘!6款利器助你高效研发

"研发管理工具有哪些&#xff1f;6款研发管理利器分析Zoho Projects、Trello、Asana、Monday.com、Smartsheet、Jira。" 在如今的科技发展日新月异的时代&#xff0c;研发管理工具的重要性日益凸显。研发管理工具有助于提高研发效率&#xff0c;降低成本&#xff0c;…...

如何建网站教程/郑州最好的建站公司

说明&#xff1a;蓝色命令名称浅绿命令参数浅蓝选项紫色目录系统环境&#xff1a;CentOS 5.5 x86_64python版本&#xff1a;Python 2.7.3最近很多网站被屏蔽了&#xff0c;感觉很不方便&#xff0c;研究研究了paramiko的代码写了一个小的代理工具。&#xff08;bug很多改进中&…...

成都手机号码销售网站建设/北京seo公司工作

原文地址&#xff1a;Go-翻过的一些面试题目 1、以下代码会输出什么&#xff1f;请简要说明。 var c make(chan int) var a intfunc f() {a 1<-c }func main() {go f()c <- 0print(a) } 能正确输出1&#xff0c;不过主协程会阻塞 f() 函数的执行。 2、以下代码会输…...

python做网站多少钱/seo搜索优化网站推广排名

爱生气的书店老板 当窗口移动时&#xff0c;只需关心移进窗口的和移出窗口的两个值即可。 class Solution { public:int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int X) {int ans 0,n customers.size(),cnt 0,res 0;for(int i0;i…...

深圳网站建设哪些/今日刚刚发生新闻事件

文章目录 1)、为什么要自定义UITabBarController2)、重复代码的抽取3)、统一所有控制器导航栏左上角和右上角的内容4)、"duplicate symbol _OBJC_METACLASS_$_类名 in:"错误的解决方案5)、创建UIBarButtonItem的代码为什么放在UIBarButtonItem分类中最合适?6)iOS开…...

5000多一年的网站建站/哪个公司的网站制作

环境&#xff1a;Navicat Premium 15、 win10 1909 一、创建表空间 二、创建用户并授权 三、建表&#xff0c;插入数据 四、其他命令 创建表空间 -- 创建表空间并设置表空间名 CREATE tablespace "表空间名" -- 设置文件名 建议和表空间名保持一致 datafile xxx 或…...

珠海政府网站大湾区建设/济南网站seo哪家公司好

import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class ConditionCommunication {/*** 这一个示例的学习应该和传统的线程通信相互对比&#xff0c;Condition的通信优点查看文…...