文章目录

  • 一、Flowable介绍
  • 1.简介
  • 2.Activiti、Flowable、Camunda
  • 二、Flowable实战(集成Flowable Modeler)
  • 三、流程的创建和使用
  • 1.BPMN基本概念介绍
  • 2.业务模型流程创建
  • 3.表单创建及使用
  • 4.流程的使用
  • 5.核心表介绍
  • 四、常见报错解决
  • 1.自动建表提示 表已存在 Table 'act_id_property' already exists
  • 2.集成SpringBoot项目报错 "SLF4J: Class path contains multiple SLF4J bindings."
  • 3.集成SpringBoot的项目报错"NoClassDefFoundError: org/springframework/core/ErrorCoded"
  • 4.返回task的List对象时报错:Could not write content: lazy loading outside command context

一、Flowable介绍

1.简介

Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义, 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。

BPMN:Business Process Modeling Notation,即业务流程建模符号,是一种流程建模的通用和标准语言,用来绘制业务流程图,以便更好地让各部门之间理解业务流程和相互关系。

2.Activiti、Flowable、Camunda

(1)为什么选这三者比较?
三者都是开源免费、社区活跃度比较高的;
三者都是同一个团队的分支,camunda基于activiti5,flowable基于activiti6,activiti5则是基于更早的jbpm4;

(2)优缺点
①功能比较

②Activiti7以后,对于流程引擎本身及相关引擎功能关注度并不高,核心很大精力放在构建其云生态环境(适配Docker、kubernates,适配Jenkins等devops工具);而Flowable分离出去做了很多引擎相关的完善。

③网上资料数对比,仅以GIT为例:
git上activiti的项目(16,618)是flowable(59629)两三倍,flowable是camunda(4077)十来倍

综上所述,Activiti7最大的优势是网上资料多,缺点是功能最少、易用性比较差。camunda最大的优势就是性能比较高,缺点是三者的资料是最少的。flowable是一个比较均衡的方案。

二、Flowable实战(集成Flowable Modeler)

你也可以选择不集成modeler编辑器,那样的话只需要引入flowable的starter和添加ProcessEngineConfig配置,在后面的流程的创建和使用中也能运行

1.源码下载,后面会用到
地址:https://github.com/flowable/flowable-engine/releases/tag/flowable-6.4.1/

2.引入依赖,我这里用的版本是6.4.1,替换下面的参数即可

       <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.1.3.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.1.3.RELEASE</version><scope>test</scope></dependency><!-- Flowable spring-boot 版套餐 --><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter-basic</artifactId><version>${flowable.version}</version></dependency><!-- flowable 集成依赖 rest,logic,conf --><dependency><groupId>org.flowable</groupId><artifactId>flowable-ui-modeler-rest</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-ui-modeler-logic</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-ui-modeler-conf</artifactId><version>${flowable.version}</version></dependency><!-- flowable 集成依赖 engine --><!--<dependency><groupId>org.flowable</groupId><artifactId>flowable-engine</artifactId><version>${flowable.version}</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><!-- 配置文件处理器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>3.0.1</version></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version><scope>provided</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.24</version><scope>compile</scope></dependency><!--数据库连接--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.1.3.RELEASE</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version></dependency></dependencies>

3.后端代码集成
需要这些文件,其中Security的包名不能变,否则不能生效,无法免登录

AppDispatcherServletConfiguration.java

/* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.example.flowable.config;import org.flowable.ui.modeler.rest.app.EditorGroupsResource;
import org.flowable.ui.modeler.rest.app.EditorUsersResource;
import org.flowable.ui.modeler.rest.app.StencilSetResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;@Configuration
@ComponentScan(value = {"org.flowable.ui.modeler.rest.app",// 不加载 rest,因为 getAccount 接口需要我们自己实现
// "org.flowable.ui.common.rest"
},excludeFilters = {// 移除 EditorUsersResource 与 EditorGroupsResource,因为不使用 IDM 部分@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = EditorUsersResource.class),@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = EditorGroupsResource.class),// 配置文件用自己的@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StencilSetResource.class),
}
)
@EnableAsync
public class AppDispatcherServletConfiguration implements WebMvcRegistrations {private static final Logger LOGGER = LoggerFactory.getLogger(AppDispatcherServletConfiguration.class);@Beanpublic SessionLocaleResolver localeResolver() {return new SessionLocaleResolver();}@Beanpublic LocaleChangeInterceptor localeChangeInterceptor() {LOGGER.debug("Configuring localeChangeInterceptor");LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();localeChangeInterceptor.setParamName("language");return localeChangeInterceptor;}@Overridepublic RequestMappingHandlerMapping getRequestMappingHandlerMapping() {LOGGER.debug("Creating requestMappingHandlerMapping");RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();requestMappingHandlerMapping.setUseSuffixPatternMatch(false);requestMappingHandlerMapping.setRemoveSemicolonContent(false);Object[] interceptors = { localeChangeInterceptor() };requestMappingHandlerMapping.setInterceptors(interceptors);return requestMappingHandlerMapping;}
}

ApplicationConfiguration.java

/* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.example.flowable.config;import org.flowable.ui.common.service.idm.RemoteIdmService;
import org.flowable.ui.modeler.properties.FlowableModelerAppProperties;
import org.flowable.ui.modeler.servlet.ApiDispatcherServletConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
@Configuration
@EnableConfigurationProperties(FlowableModelerAppProperties.class)
@ComponentScan(basePackages = {// "org.flowable.ui.modeler.conf", // 不引入 conf"org.flowable.ui.modeler.repository","org.flowable.ui.modeler.service",
// "org.flowable.ui.modeler.security", //授权方面的都不需要
// "org.flowable.ui.common.conf", // flowable 开发环境内置的数据库连接
// "org.flowable.ui.common.filter", // IDM 方面的过滤器"org.flowable.ui.common.service","org.flowable.ui.common.repository",//
// "org.flowable.ui.common.security",//授权方面的都不需要"org.flowable.ui.common.tenant" },excludeFilters = {// 移除 RemoteIdmService@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = RemoteIdmService.class)
}
)
public class ApplicationConfiguration {@Beanpublic ServletRegistrationBean modelerApiServlet(ApplicationContext applicationContext) {AnnotationConfigWebApplicationContext dispatcherServletConfiguration = new AnnotationConfigWebApplicationContext();dispatcherServletConfiguration.setParent(applicationContext);dispatcherServletConfiguration.register(ApiDispatcherServletConfiguration.class);DispatcherServlet servlet = new DispatcherServlet(dispatcherServletConfiguration);ServletRegistrationBean registrationBean = new ServletRegistrationBean(servlet, "/api/*");registrationBean.setName("Flowable Modeler App API Servlet");registrationBean.setLoadOnStartup(1);registrationBean.setAsyncSupported(true);return registrationBean;}
}

FlowableStencilSetResource.java

/* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.example.flowable.config;import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.flowable.ui.common.service.exception.InternalServerErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/app")
public class FlowableStencilSetResource {private static final Logger LOGGER = LoggerFactory.getLogger(FlowableStencilSetResource.class);@Autowiredprotected ObjectMapper objectMapper;@RequestMapping(value = "/rest/stencil-sets/editor", method = RequestMethod.GET, produces = "application/json")public JsonNode getStencilSetForEditor() {try {JsonNode stencilNode = objectMapper.readTree(this.getClass().getClassLoader().getResourceAsStream("stencilset/stencilset_bpmn.json"));return stencilNode;} catch (Exception e) {LOGGER.error("Error reading bpmn stencil set json", e);throw new InternalServerErrorException("Error reading bpmn stencil set json");}}@RequestMapping(value = "/rest/stencil-sets/cmmneditor", method = RequestMethod.GET, produces = "application/json")public JsonNode getCmmnStencilSetForEditor() {try {JsonNode stencilNode = objectMapper.readTree(this.getClass().getClassLoader().getResourceAsStream("stencilset/stencilset_cmmn.json"));return stencilNode;} catch (Exception e) {LOGGER.error("Error reading bpmn stencil set json", e);throw new InternalServerErrorException("Error reading bpmn stencil set json");}}
}

ProcessEngineConfig.java

package com.example.flowable.config;import lombok.Data;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;/** * 流程引擎配置文件 * @author: jijh * @create: 2022-12-12 16:49 **/
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
@Data
public class ProcessEngineConfig {private Logger logger = LoggerFactory.getLogger(ProcessEngineConfig.class);private String url;private String driverClassName;private String username;private String password;private String publicKey;/** * 初始化流程引擎 * @return */@Primary@Bean(name = "processEngine")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_TRUE)// 默认邮箱配置// 发邮件的主机地址,先用 QQ 邮箱//.setMailServerHost("smtp.qq.com")// POP3/SMTP服务的授权码//.setMailServerPassword("xxxxxxx")// 默认发件人//.setMailServerDefaultFrom("836369078@qq.com")// 设置发件人用户名//.setMailServerUsername("管理员")// 解决流程图乱码.setActivityFontName("宋体").setLabelFontName("宋体").setAnnotationFontName("宋体");} catch (Exception e) {e.printStackTrace();}// 初始化流程引擎对象ProcessEngine processEngine = cfg.buildProcessEngine();logger.info("=============================ProcessEngineEnd=============================");return processEngine;}
}

FlowableApplication.java

package com.example.flowable;import com.example.flowable.config.AppDispatcherServletConfiguration;
import com.example.flowable.config.ApplicationConfiguration;
import org.flowable.ui.modeler.conf.DatabaseConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Import;//启用全局异常拦截器
@Import(value={// 引入修改的配置ApplicationConfiguration.class,AppDispatcherServletConfiguration.class,// 引入 DatabaseConfiguration 表更新转换DatabaseConfiguration.class})
// Eureka 客户端
@EnableDiscoveryClient
@MapperScan("com.example.*.dao")
// 移除 Security 自动配置
// Spring Cloud 为 Finchley 版本
// @SpringBootApplication(exclude={SecurityAutoConfiguration.class})
// Spring Cloud 为 Greenwich 版本
@SpringBootApplication(exclude={SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class})public class FlowableApplication {public static void main(String[] args) {SpringApplication.run(FlowableApplication.class, args);}}

SecurityUtils.java

/* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package org.flowable.ui.common.security;import org.flowable.idm.api.User;
import org.flowable.ui.common.model.RemoteUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;import java.util.ArrayList;
import java.util.List;/*** Utility class for Spring Security.*/
public class SecurityUtils {private static User assumeUser;private SecurityUtils() {}/*** Get the login of the current user.*/public static String getCurrentUserId() {User user = getCurrentUserObject();if (user != null) {return user.getId();}return null;}/*** @return the {@link User} object associated with the current logged in user.*/public static User getCurrentUserObject() {if (assumeUser != null) {return assumeUser;}RemoteUser user = new RemoteUser();user.setId("admin");user.setDisplayName("Administrator");user.setFirstName("Administrator");user.setLastName("Administrator");user.setEmail("admin@flowable.com");user.setPassword("123456");List<String> pris = new ArrayList<>();pris.add(DefaultPrivileges.ACCESS_MODELER);pris.add(DefaultPrivileges.ACCESS_IDM);pris.add(DefaultPrivileges.ACCESS_ADMIN);pris.add(DefaultPrivileges.ACCESS_TASK);pris.add(DefaultPrivileges.ACCESS_REST_API);user.setPrivileges(pris);return user;}public static FlowableAppUser getCurrentFlowableAppUser() {FlowableAppUser user = null;SecurityContext securityContext = SecurityContextHolder.getContext();if (securityContext != null && securityContext.getAuthentication() != null) {Object principal = securityContext.getAuthentication().getPrincipal();if (principal instanceof FlowableAppUser) {user = (FlowableAppUser) principal;}}return user;}public static boolean currentUserHasCapability(String capability) {FlowableAppUser user = getCurrentFlowableAppUser();for (GrantedAuthority grantedAuthority : user.getAuthorities()) {if (capability.equals(grantedAuthority.getAuthority())) {return true;}}return false;}public static void assumeUser(User user) {assumeUser = user;}public static void clearAssumeUser() {assumeUser = null;}}

4.前端代码集成
目录结构如下:

static下的代码来自源码包的flowable-engine-flowable-6.4.1\modules\flowable-ui-modeler\flowable-ui-modeler-app\src\main\resources\static下面

resource\static\scripts\configuration\url-conf.js需要修改:

/* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
var FLOWABLE = FLOWABLE || {};/** Contains methods to retrieve the (mostly) base urls of the different end points.* Two of the methods #getImageUrl and #getModelThumbnailUrl are exposed in the $rootScope for usage in the HTML views.*/
FLOWABLE.APP_URL = {/* ACCOUNT URLS */getAccountUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/login/rest/account';},getLogoutUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/logout';},/* MODEL URLS */getModelsUrl: function (query) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models' + (query || "");},getModelUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId;},getModelModelJsonUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/model-json';},getModelBpmn20ExportUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/bpmn20?version=' + Date.now();},getCloneModelsUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/clone';},getModelHistoriesUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/history';},getModelHistoryUrl: function (modelId, modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/history/' + modelHistoryId;},getModelHistoryModelJsonUrl: function (modelId, modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/history/' + modelHistoryId + '/model-json';},getModelHistoryBpmn20ExportUrl: function (modelId, modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/history/' + modelHistoryId + '/bpmn20?version=' + Date.now();},getCmmnModelDownloadUrl: function (modelId, modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + (modelHistoryId ? '/history/' + modelHistoryId : '') + '/cmmn?version=' + Date.now();},getModelParentRelationsUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/parent-relations';},/* APP DEFINITION URLS  */getAppDefinitionImportUrl: function (renewIdmIds) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/import?renewIdmEntries=' + renewIdmIds;},getAppDefinitionTextImportUrl: function (renewIdmIds) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/text/import?renewIdmEntries=' + renewIdmIds;},getAppDefinitionUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId;},getAppDefinitionModelImportUrl: function (modelId, renewIdmIds) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId + '/import?renewIdmEntries=' + renewIdmIds;},getAppDefinitionModelTextImportUrl: function (modelId, renewIdmIds) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId + '/text/import?renewIdmEntries=' + renewIdmIds;},getAppDefinitionPublishUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId + '/publish';},getAppDefinitionExportUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId + '/export?version=' + Date.now();},getAppDefinitionBarExportUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId + '/export-bar?version=' + Date.now();},getAppDefinitionHistoryUrl: function (modelId, historyModelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/app-definitions/' + modelId + '/history/' + historyModelId;},getModelsForAppDefinitionUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models-for-app-definition';},getCmmnModelsForAppDefinitionUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/cmmn-models-for-app-definition';},/* PROCESS INSTANCE URLS */getProcessInstanceModelJsonUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/process-instances/' + modelId + '/model-json';},getProcessInstanceModelJsonHistoryUrl: function (historyModelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/process-instances/history/' + historyModelId + '/model-json';},/* PROCESS DEFINITION URLS */getProcessDefinitionModelJsonUrl: function (processDefinitionId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/process-definitions/' + processDefinitionId + '/model-json';},/* PROCESS MODEL URLS */getImportProcessModelUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/import-process-model';},getImportProcessModelTextUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/import-process-model/text';},/* DECISION TABLE URLS */getDecisionTableModelsUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models';},getDecisionTableImportUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models/import-decision-table';},getDecisionTableTextImportUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models/import-decision-table-text';},getDecisionTableModelUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models/' + modelId;},getDecisionTableModelValuesUrl: function (query) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models/values?' + query;},getDecisionTableModelsHistoryUrl: function (modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models/history/' + modelHistoryId;},getDecisionTableModelHistoryUrl: function (modelId, modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/decision-table-models/' + modelId + '/history/' + modelHistoryId;},/* FORM MODEL URLS */getFormModelsUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/form-models';},getFormModelValuesUrl: function (query) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/form-models/values?' + query;},getFormModelUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/form-models/' + modelId;},getFormModelHistoryUrl: function (modelId, modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/form-models/' + modelId + '/history/' + modelHistoryId;},/* CASE MODEL URLS */getCaseModelsUrl: function (query) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/case-models' + (query || "");},getCaseModelImportUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/import-case-model';},getCaseModelTextImportUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/import-case-model/text';},getCaseInstancesHistoryModelJsonUrl: function (modelHistoryId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/case-instances/history/' + modelHistoryId + '/model-json';},getCaseInstancesModelJsonUrl: function (modelId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/case-instances/' + modelId + '/model-json';},getCaseDefinitionModelJsonUrl: function (caseDefinitionId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/case-definitions/' + caseDefinitionId + '/model-json';},/* IMAGE URLS (exposed in rootscope in app.js */getImageUrl: function (imageId) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/image/' + imageId;},getModelThumbnailUrl: function (modelId, version) {return FLOWABLE.CONFIG.contextRoot + '/app/rest/models/' + modelId + '/thumbnail' + (version ? "?version=" + version : "");},/* OTHER URLS */getEditorUsersUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/editor-users';},getEditorGroupsUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/editor-groups';},getAboutInfoUrl: function () {return FLOWABLE.CONFIG.contextRoot + '/app/rest/about-info';}};

stencilset下面的文件是汉化文件
https://download.csdn.net/download/tttalk/87347577
5.其余配置
application.yml

spring:application:name: flowable-servicemain:allow-circular-references: truedatasource:url: jdbc:mysql://127.0.0.1:3306/flowable2?serverTimezone=Asia/Shanghai&useUnicode=true&nullCatalogMeansCurrent=true&characterEncoding=UTF-8&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource# flowable 配置
flowable:# 关闭异步,不关闭历史数据的插入就是异步的,会在同一个事物里面,无法回滚# 开发可开启会提高些效率,上线需要关闭async-executor-activate: false

log4j.properties

log4j.rootLogger=DEBUG, CA
log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{
hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

6.启动项目
进入http://localhost:8080/,进入下面页面则算启动成功了

三、流程的创建和使用

1.BPMN基本概念介绍

可以去BPMN官网学习相关知识 https://www.bpmn.org/
(1)流对象(Flow Objects):是定义业务流程的主要图形元素,包括三种:事件、活动、网关

事件(Events):指的是在业务流程的运行过程中发生的事情,分为:
开始:表示一个流程的开始
中间:发生的开始和结束事件之间,影响处理的流程
结束:表示该过程结束

活动(Activities):包括任务和子流程两类。子流程在图形的下方中间外加一个小加号(+)来区分。

网关(Gateways):用于表示流程的分支与合并。

排他网关:只有一条路径会被选择
并行网关:所有路径会被同时选择
包容网关:可以同时执行多条线路,也可以在网关上设置条件
事件网关:专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。

(2)数据(Data):数据主要通过四种元素表示

数据对象(Data Objects)
数据输入(Data Inputs)
数据输出(Data Outputs)
数据存储(Data Stores)

(3)连接对象(Connecting Objects):流对象彼此互相连接或者连接到其他信息的方法主要有三种

顺序流:用一个带实心箭头的实心线表示,用于指定活动执行的顺序

信息流:用一条带箭头的虚线表示,用于描述两个独立的业务参与者(业务实体/业务角色)之间发送和接受的消息流动

关联:用一根带有线箭头的点线表示,用于将相关的数据、文本和其他人工信息与流对象联系起来。用于展示活动的输入和输出

(4)泳道(Swimlanes):通过泳道对主要的建模元素进行分组,将活动划分到不同的可视化类别中来描述由不同的参与者的责任与职责。

2.业务模型流程创建

我这里自己创建了一个流程,如果自己嫌麻烦可以直接使用我的(右上角导入)BPMN的XML文件即可,但是form不会生效

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef"><process id="a999" name="报销审批流程" isExecutable="true"><documentation>报销审批</documentation><startEvent id="startEvent1"></startEvent><userTask id="sid-5946EBF9-CCA9-41D5-A1B9-812886784183" name="用户申请 " flowable:candidateUsers="userid1,userid2" flowable:formKey="form999"></userTask><sequenceFlow id="sid-62D0C9DD-539D-45C2-A46F-8F382C931ED6" sourceRef="startEvent1" targetRef="sid-5946EBF9-CCA9-41D5-A1B9-812886784183"></sequenceFlow><userTask id="sid-8353A778-A852-48DC-A39F-EDB79EE618CF" name="部门领导审核" flowable:candidateUsers="leader1,leader2"></userTask><userTask id="sid-197A3224-407C-44E4-959E-2EF7C098AD1D" name="人事部门审核"></userTask><endEvent id="sid-D3E07881-6D59-44CC-9E0D-D8D4CC34868E"></endEvent><exclusiveGateway id="sid-15A21F4D-DBEC-4D57-B62D-F34B9388209C"></exclusiveGateway><exclusiveGateway id="sid-DE78AEF5-DFA7-44CA-9938-6DFB6A9FFF83"></exclusiveGateway><sequenceFlow id="sid-81BBF9CE-1366-4DAF-AA94-EB8C6C200BF1" sourceRef="sid-197A3224-407C-44E4-959E-2EF7C098AD1D" targetRef="sid-DE78AEF5-DFA7-44CA-9938-6DFB6A9FFF83"></sequenceFlow><sequenceFlow id="sid-E4554206-B35E-404B-98DA-05E47430E5EF" sourceRef="sid-5946EBF9-CCA9-41D5-A1B9-812886784183" targetRef="sid-15A21F4D-DBEC-4D57-B62D-F34B9388209C"></sequenceFlow><exclusiveGateway id="sid-3CC1F0CA-3862-4840-BD63-F3F34068BE7B"></exclusiveGateway><sequenceFlow id="sid-CD4D2389-39C4-4C8D-AFCD-CBFA528B3D0C" sourceRef="sid-8353A778-A852-48DC-A39F-EDB79EE618CF" targetRef="sid-3CC1F0CA-3862-4840-BD63-F3F34068BE7B"></sequenceFlow><sequenceFlow id="sid-F18F027C-B121-4672-BB0E-B93D39F0F09E" name="通过" sourceRef="sid-15A21F4D-DBEC-4D57-B62D-F34B9388209C" targetRef="sid-8353A778-A852-48DC-A39F-EDB79EE618CF"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approval == '1'}]]></conditionExpression></sequenceFlow><sequenceFlow id="sid-A18D3D02-1C7D-4B3D-A57F-D3CB1B29E6E8" name="退回" sourceRef="sid-15A21F4D-DBEC-4D57-B62D-F34B9388209C" targetRef="sid-5946EBF9-CCA9-41D5-A1B9-812886784183"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approval == '0'}]]></conditionExpression></sequenceFlow><sequenceFlow id="sid-BF3D19F8-EFC7-47D0-BB96-87A26734B2B4" name="通过" sourceRef="sid-3CC1F0CA-3862-4840-BD63-F3F34068BE7B" targetRef="sid-197A3224-407C-44E4-959E-2EF7C098AD1D"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approval == '1'}]]></conditionExpression></sequenceFlow><sequenceFlow id="sid-BA470DA5-7754-40AC-9823-0A4E901BF2B1" name="退回" sourceRef="sid-3CC1F0CA-3862-4840-BD63-F3F34068BE7B" targetRef="sid-8353A778-A852-48DC-A39F-EDB79EE618CF"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approval == '0'}]]></conditionExpression></sequenceFlow><sequenceFlow id="sid-8406E503-FF81-47A9-9B08-73BB6CB37926" name="通过" sourceRef="sid-DE78AEF5-DFA7-44CA-9938-6DFB6A9FFF83" targetRef="sid-D3E07881-6D59-44CC-9E0D-D8D4CC34868E"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approval == '1'}]]></conditionExpression></sequenceFlow><sequenceFlow id="sid-9C84DFB1-9ECA-4202-ADEE-7176A12050BC" name="退回初始状态  " sourceRef="sid-DE78AEF5-DFA7-44CA-9938-6DFB6A9FFF83" targetRef="sid-5946EBF9-CCA9-41D5-A1B9-812886784183"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approval == '0'}]]></conditionExpression></sequenceFlow></process><bpmndi:BPMNDiagram id="BPMNDiagram_a999"><bpmndi:BPMNPlane bpmnElement="a999" id="BPMNPlane_a999"><bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1"><omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-5946EBF9-CCA9-41D5-A1B9-812886784183" id="BPMNShape_sid-5946EBF9-CCA9-41D5-A1B9-812886784183"><omgdc:Bounds height="80.0" width="100.0" x="165.0" y="139.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-8353A778-A852-48DC-A39F-EDB79EE618CF" id="BPMNShape_sid-8353A778-A852-48DC-A39F-EDB79EE618CF"><omgdc:Bounds height="80.0" width="100.0" x="435.0" y="139.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-197A3224-407C-44E4-959E-2EF7C098AD1D" id="BPMNShape_sid-197A3224-407C-44E4-959E-2EF7C098AD1D"><omgdc:Bounds height="80.0" width="100.0" x="705.0" y="139.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-D3E07881-6D59-44CC-9E0D-D8D4CC34868E" id="BPMNShape_sid-D3E07881-6D59-44CC-9E0D-D8D4CC34868E"><omgdc:Bounds height="28.0" width="28.0" x="1050.0" y="165.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-15A21F4D-DBEC-4D57-B62D-F34B9388209C" id="BPMNShape_sid-15A21F4D-DBEC-4D57-B62D-F34B9388209C"><omgdc:Bounds height="40.0" width="40.0" x="330.0" y="158.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-DE78AEF5-DFA7-44CA-9938-6DFB6A9FFF83" id="BPMNShape_sid-DE78AEF5-DFA7-44CA-9938-6DFB6A9FFF83"><omgdc:Bounds height="40.0" width="40.0" x="900.0" y="159.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="sid-3CC1F0CA-3862-4840-BD63-F3F34068BE7B" id="BPMNShape_sid-3CC1F0CA-3862-4840-BD63-F3F34068BE7B"><omgdc:Bounds height="40.0" width="40.0" x="585.0" y="159.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNEdge bpmnElement="sid-E4554206-B35E-404B-98DA-05E47430E5EF" id="BPMNEdge_sid-E4554206-B35E-404B-98DA-05E47430E5EF"><omgdi:waypoint x="264.94999999999675" y="178.62962962962962"></omgdi:waypoint><omgdi:waypoint x="330.14705882352825" y="178.1466911764706"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-A18D3D02-1C7D-4B3D-A57F-D3CB1B29E6E8" id="BPMNEdge_sid-A18D3D02-1C7D-4B3D-A57F-D3CB1B29E6E8"><omgdi:waypoint x="350.0" y="197.93754681647943"></omgdi:waypoint><omgdi:waypoint x="350.0" y="258.0"></omgdi:waypoint><omgdi:waypoint x="215.0" y="258.0"></omgdi:waypoint><omgdi:waypoint x="215.0" y="218.95000000000002"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-9C84DFB1-9ECA-4202-ADEE-7176A12050BC" id="BPMNEdge_sid-9C84DFB1-9ECA-4202-ADEE-7176A12050BC"><omgdi:waypoint x="920.0" y="159.0"></omgdi:waypoint><omgdi:waypoint x="920.0" y="56.0"></omgdi:waypoint><omgdi:waypoint x="215.0" y="56.0"></omgdi:waypoint><omgdi:waypoint x="215.0" y="139.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-62D0C9DD-539D-45C2-A46F-8F382C931ED6" id="BPMNEdge_sid-62D0C9DD-539D-45C2-A46F-8F382C931ED6"><omgdi:waypoint x="129.94919380537883" y="178.14949271315584"></omgdi:waypoint><omgdi:waypoint x="164.99999999999716" y="178.49999999999997"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-81BBF9CE-1366-4DAF-AA94-EB8C6C200BF1" id="BPMNEdge_sid-81BBF9CE-1366-4DAF-AA94-EB8C6C200BF1"><omgdi:waypoint x="804.9499999999836" y="179.0"></omgdi:waypoint><omgdi:waypoint x="900.0" y="179.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-BA470DA5-7754-40AC-9823-0A4E901BF2B1" id="BPMNEdge_sid-BA470DA5-7754-40AC-9823-0A4E901BF2B1"><omgdi:waypoint x="605.0" y="198.93754681647943"></omgdi:waypoint><omgdi:waypoint x="605.0" y="259.0"></omgdi:waypoint><omgdi:waypoint x="485.0" y="259.0"></omgdi:waypoint><omgdi:waypoint x="485.0" y="218.95000000000002"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-8406E503-FF81-47A9-9B08-73BB6CB37926" id="BPMNEdge_sid-8406E503-FF81-47A9-9B08-73BB6CB37926"><omgdi:waypoint x="939.9430777238028" y="179.0"></omgdi:waypoint><omgdi:waypoint x="1050.0" y="179.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-CD4D2389-39C4-4C8D-AFCD-CBFA528B3D0C" id="BPMNEdge_sid-CD4D2389-39C4-4C8D-AFCD-CBFA528B3D0C"><omgdi:waypoint x="534.95" y="179.0"></omgdi:waypoint><omgdi:waypoint x="585.0" y="179.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-F18F027C-B121-4672-BB0E-B93D39F0F09E" id="BPMNEdge_sid-F18F027C-B121-4672-BB0E-B93D39F0F09E"><omgdi:waypoint x="369.79608743570697" y="178.14669117647063"></omgdi:waypoint><omgdi:waypoint x="435.0" y="178.62962962962965"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="sid-BF3D19F8-EFC7-47D0-BB96-87A26734B2B4" id="BPMNEdge_sid-BF3D19F8-EFC7-47D0-BB96-87A26734B2B4"><omgdi:waypoint x="624.943354430356" y="179.0"></omgdi:waypoint><omgdi:waypoint x="705.0" y="179.0"></omgdi:waypoint></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>


折线绘制需要用到这个:
每一个节点都需要分配用户

我这里id直接写死了,这个在代码里可以自己指派

分配完去左上角验证,没有什么警告之类的就可以

3.表单创建及使用

这个功能可能使用的比较少,一般在前端系统开发自己的表单
进入http://localhost:8080/#/forms


在流程中使用,只需要填表单key即可,记得分配用户

4.流程的使用

流程和表单等保存在act_de_model,可以去数据库里查看
(1)代码实现

在recource下的新建process放入我们刚刚画好的流程图

FlowableController .java

package com.example.flowable.controller;import com.example.flowable.service.FlowService;
import com.google.common.collect.Maps;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.Task;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/flowable")
public class FlowableController {@Resourceprivate FlowService flowService;//部署流程@RequestMapping(value = "/createProcess/{processDefinitionKey}", method = {RequestMethod.GET})public String createProcess(@PathVariable("processDefinitionKey") String processDefinitionKey) {//已知processDefinitionKey,可以通过act_de_model获取processName,我这里偷懒写死了String processName = "报销审批流程";String path = "process/"+processName+".bpmn20.xml";flowService.createProcess(processDefinitionKey,path);return "流程部署成功";}//发起流程@RequestMapping(value = "/apply/{processDefinitionKey}", method = {RequestMethod.GET})public String apply(@PathVariable("processDefinitionKey") String processDefinitionKey) throws Exception {Map<String,Object> map = new HashMap<>();String processId = flowService.applyProcess(processDefinitionKey,map);System.out.println(processId);//17501return "流程发起成功,流程id为"+processId;}//生成流程图,标红的框是当前流程走到的地方@RequestMapping(value = "/getPng/{processId}", method = {RequestMethod.GET})public String getPng(@PathVariable("processId") String processId) throws Exception {ByteArrayOutputStream out = flowService.genProcessDiagram(processId);FileOutputStream fileOutputStream = new FileOutputStream("D:\\process" + processId + ".png");fileOutputStream.write(out.toByteArray());return "流程图生成成功";}//查询待办流程列表@RequestMapping(value = "/flowList/{userId}", method = {RequestMethod.GET})public Object flowList(@PathVariable("userId") String userId) {List<Task> list = flowService.todoList(userId);System.out.println(list.toString());return list.toString();}//流程审批通过或退回@RequestMapping(value = "/approveProcess/{userId}/{taskId}", method = {RequestMethod.GET})public void approveProcess(@PathVariable("taskId") String taskId,@PathVariable("userId") String userId){Map<String,Object> map = Maps.newHashMap();//这个map可以放在表单里传过来map.put("approval","1");//map.put("approval","0");flowService.approveProcess(taskId,userId,map);}//流程退回某一结点@RequestMapping(value = "/withdrawProcess/{taskId}/{nodeId}", method = {RequestMethod.GET})public void withdrawProcess(@PathVariable("taskId") String taskId,@PathVariable("nodeId") String nodeId) throws Exception {flowService.withdrawProcess(taskId,nodeId);}//查询历史@RequestMapping(value = "/historyList/{processId}", method = {RequestMethod.GET})public Object historyList(@PathVariable("processId") String processId){List<HistoricActivityInstance>list = flowService.historyList(processId);return list.toString();}}

FlowService .java

package com.example.flowable.service;import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.Task;import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Map;public interface FlowService {/*** 部署流程* @param processName 流程定义名* @param resourcePath 如flowable/process.bpmn* @return*/public void createProcess(String processName, String resourcePath);/*** 发起流程* @param processName 流程定义名* @param map 参数* @return 流程实例ID*/public String applyProcess(String processName,Map<String,Object> map);/*** 查询某用户/群体/角色待办列表* @return*/public List<Task> todoList(String user);/*** 生成流程图* @param processId 任务ID*/public ByteArrayOutputStream genProcessDiagram(String processId) throws Exception;/*** 完成任务* @param taskId 任务id* @param user 用户/角色id* @param map 流程变量*/public void approveProcess(String taskId,String user,Map<String,Object> map);/*** 将节点移动到任意节点上* @param taskId 任务id* @param taskDefinitionKey 目标节点ID,节点ID在流程画图的时候设置好* @return*/public void withdrawProcess(String taskId,String taskDefinitionKey) throws Exception;/*** 获取流程的历史节点列表* 获取的是这个流程实例走过的节点,当然也可以获取到开始节点、网关、线等信息,下面是只过滤了用户任务节点"userTask"的信息* @param processId 流程ID* @return*/public List<HistoricActivityInstance> historyList(String processId);/*** 删除流程实例* @param processId 流程实例ID* @return*/public void deleteProcess(String processId);/*** 申领任务* 其实申领的意思就是当在一个用户组中所有有这个权限的用户都可以同时看到这个待办信息,* 这个待办信息可以理解为公布出来的任务,需要有人去领取这个任务,那么一旦领取这个任务,其他有这个节点操作权限的用户就不会看到这个待办信息,* 因为已经被这个用户领取了* @param taskId* @param user* @return*/public void claim(String taskId,String user);/*** 取消申领任务* 一旦取消申领,那么有这个节点操作权限的用户在待办上又可以看到,* 申领和取消申领是一种锁定机制,使得多个用户在待办操作上不会出现执行同一个当前节点的任务* @param taskId* @return*/public void unClaim(String taskId);
}

FlowServiceImpl .java

package com.example.flowable.service.impl;import com.example.flowable.service.FlowService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.Deployment;
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.stereotype.Service;import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Slf4j
@Service
public class FlowServiceImpl implements FlowService {@Resourceprivate RepositoryService repositoryService;@Resourceprivate RuntimeService runtimeService;@Resourceprivate TaskService taskService;@Resourceprivate ProcessEngine processEngine;@Resourceprivate HistoryService historyService;/*** 部署流程* @param processName 流程定义名* @param resourcePath 如flowable/process.bpmn* @return*/@Overridepublic void createProcess(String processName, String resourcePath){Deployment deployment = repositoryService.createDeployment().name(processName).addClasspathResource(resourcePath).deploy();}/*** 发起流程* @return*/@Overridepublic String applyProcess(String processName,Map<String,Object>map){//指定发起人ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processName, map);log.info("流程实例ID:"+processInstance.getProcessInstanceId());return processInstance.getProcessInstanceId();}/*** 查询某用户/群体/角色待办列表* @return*/@Overridepublic List<Task> todoList(String user){List<Task> tasks = taskService.createTaskQuery().taskCandidateOrAssigned(user).orderByTaskCreateTime().desc().list();return tasks;}/*** 生成流程图* @param processId 任务ID*/@Overridepublic ByteArrayOutputStream genProcessDiagram(String processId) throws Exception {ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();//流程走完的不显示图if (pi == null) {return null;}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,"bmp", activityIds,flows,"宋体","宋体","宋体",null,1.0,false);ByteArrayOutputStream out = null;byte[] buf = new byte[1024];int legth = 0;try {out = new ByteArrayOutputStream();while ((legth = in.read(buf)) != -1) {out.write(buf, 0, legth);}return out;} finally {if (in != null) {in.close();}if (out != null) {out.close();}}}/*** 完成任务* @param taskId 任务id* @param user 用户/角色id* @param map 流程变量*/@Overridepublic void approveProcess(String taskId,String user,Map<String,Object> map){//先申领任务,相当于用户将这个流程任务占用,其他在这个用户组里的用户不能看到该流程任务taskService.claim(taskId,user);//再流转下一个节点taskService.complete(taskId, map);}/*** 将节点移动到任意节点上* @param taskId 任务id* @param taskDefinitionKey 目标节点ID,节点ID在流程画图的时候设置好* @return*/@Overridepublic void withdrawProcess(String taskId,String taskDefinitionKey) throws Exception {//获取当前任务,让其移动到目标节点位置Task task = taskService.createTaskQuery().taskId(taskId).singleResult();if(task == null) {throw new Exception("任务不存在");}//将节点移动到目标节点runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdTo(task.getTaskDefinitionKey(), taskDefinitionKey).changeState();}/*** 获取流程的历史节点列表* 获取的是这个流程实例走过的节点,当然也可以获取到开始节点、网关、线等信息,下面是只过滤了用户任务节点"userTask"的信息* @param processId 流程ID* @return*/@Overridepublic List<HistoricActivityInstance> historyList(String processId){List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery().processInstanceId(processId).activityType("userTask").finished().orderByHistoricActivityInstanceEndTime().desc().list();return activities;}/*** 删除流程实例* @param processId 流程实例ID* @return*/@Overridepublic void deleteProcess(String processId){runtimeService.deleteProcessInstance(processId, "");}/*** 申领任务* 其实申领的意思就是当在一个用户组中所有有这个权限的用户都可以同时看到这个待办信息,* 这个待办信息可以理解为公布出来的任务,需要有人去领取这个任务,那么一旦领取这个任务,其他有这个节点操作权限的用户就不会看到这个待办信息,* 因为已经被这个用户领取了* @param taskId* @param user* @return*/@Overridepublic void claim(String taskId,String user){taskService.claim(taskId,user);}/*** 取消申领任务* 一旦取消申领,那么有这个节点操作权限的用户在待办上又可以看到,* 申领和取消申领是一种锁定机制,使得多个用户在待办操作上不会出现执行同一个当前节点的任务* @param taskId* @return*/@Overridepublic void unClaim(String taskId){taskService.unclaim(taskId);}}

(2)测试
①首先是流程部署
进入http://localhost:8080/flowable/createProcess/a999
可以看到流程部署成功,此时act_re_deployment和act_ge_bytearray会看到数据记录
②发起流程
进入http://localhost:8080/flowable/apply/a999
发起成功后act_ru_execution会有记录,并且能看到流程id,这个id后面会使用到

③生成流程图
http://localhost:8080/flowable/getPng/35005
可以看到当前流程的进度在哪,我这里把生成的流程图放在D盘下面了

④查看当前用户的待办
进入http://localhost:8080/flowable/flowList/userid2,注意userid2是我设置的用户申请里的分配用户,在流程图里是candidateUsers,可以看到task_id,后面会用到

⑤用户进行审批或退回,我这里默认写死通过
进入http://localhost:8080/flowable/approveProcess/userid2/35010
⑥再次查看流程图
http://localhost:8080/flowable/getPng/35005
可以看到流程走到下一个节点了


再次进入http://localhost:8080/flowable/flowList/userid2,发现task_id变化了

进入http://localhost:8080/flowable/flowList/leader1,发现下一节点的用户也有任务了,这个id后面会用到

⑦退回某一特定节点,其中32503是task_id,sid-5946EBF9-CCA9-41D5-A1B9-812886784183是节点id
进入http://localhost:8080/flowable/withdrawProcess/35022/sid-5946EBF9-CCA9-41D5-A1B9-812886784183

然后再次查看流程图,发现流程变化了
http://localhost:8080/flowable/getPng/35005

5.核心表介绍

(1)表名分类
ACT_RE_* repository-静态信息数据。如流程定义、流程的资源(图片,规则等)。
ACT_RU_* runtime-运行数据。存储着流程变量,用户任务,变量,职责(job)等运行时的数据。flowable只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
ACT_ID_* identity-组织机构数据。包含标识的信息,如用户,用户组,等等。
ACT_HI_* history-历史数据。包括流程实例,变量,任务,等等。
ACT_GE_* general-通用数据。各种情况都使用的数据。

(2)核心表
部署内容表:act_ge_bytearray 此表和ACT_RE_DEPLOYMENT是多对一的关系
部署ID表:act_re_deployment
流程表:act_re_procdef
历史节点表:act_hi_actinst
历史任务流程实例信息 :act_hi_taskinst
流程变量数据表:act_ru_variable
历史变量表:act_hi_varinst
流程实例历史:act_hi_procinst
历史流程人员表:act_hi_identitylink
运行时流程人员表:act_ru_identitylink
运行时任务节点表:act_ru_task

(3)流程启动到结束数据库变化
部署完毕后,act_re_deployment表中会有一条部署记录,记录这次部署的基本信息,然后是act_ge_bytearray表中有两条记录,记录的是本次上传的bpmn文件和对应的图片文件,每条记录都有act_re_deployment表的外键关联,然后是act_re_procdef表中有一条记录,记录的是该bpmn文件包含的基本信息,包含act_re_deployment表外键。

流程启动,首先向act_ru_execution表中插入一条记录,记录的是这个流程定义的执行实例,其中id和proc_inst_id相同都是流程执行实例id,也就是本次执行这个流程定义的id,包含流程定义的id外键。

然后向act_ru_task插入一条记录,记录的是第一个任务的信息,也就是开始执行第一个任务。包括act_ru_execution表中的execution_id外键和proc_inst_id外键,也就是本次执行实例id。

然后向act_hi_procinst表和act_hi_taskinst表中各插入一条记录,记录的是本次执行实例和任务的历史记录:

任务提交后,首先向act_ru_variable表中插入变量信息,包含本次流程执行实例的两个id外键,但不包括任务的id,因为setVariable方法设置的是全局变量,也就是整个流程都会有效的变量:

当流程中的一个节点任务完成后,进入下一个节点任务,act_ru_task表中这个节点任务的记录被删除,插入新的节点任务的记录。

同时act_ru_execution表中的记录并没有删除,而是将正在执行的任务变成新的节点任务。

同时向act_hi_var_inst和act_hi_taskinst插入历史记录。

整个流程执行完毕,act_ru_task,act_ru_execution和act_ru_variable表相关记录全被清空。

全程有一个表一直在记录所有动作,就是act_hi_actinst表

四、常见报错解决

1.自动建表提示 表已存在 Table ‘act_id_property’ already exists

mysql连接地址后面加上&nullCatalogMeansCurrent=true,如果还是不行,可以新建一个库再试试

2.集成SpringBoot项目报错 “SLF4J: Class path contains multiple SLF4J bindings.”

flowable 集成依赖 rest,logic,conf 的三个jar包加上下面的片段

<exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></exclusion></exclusions>

3.集成SpringBoot的项目报错"NoClassDefFoundError: org/springframework/core/ErrorCoded"

将Flowable中Spring的相关包剔除

<exclusions><exclusion><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion><exclusion><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></exclusion>
</exclusions>

4.返回task的List对象时报错:Could not write content: lazy loading outside command context

原代码:

@PostMapping(value = "/flowList")public Object flowList(@RequestBody FlowableReq req) {log.info("查询待办流程{}", JSON.toJSONString(req));List<Task> list = flowService.todoList(req.getUserId());log.info("查询待办流程成功{}", list.toString());return buildJsonWrapSuccess(list );}

懒加载只能在session打开的状况下才会正常执行,而session在service层就已经关闭了。所以在controller中返回会报错,
我们知道实体类的set类底层是一个map集合(利用Map的Key不能重复, 来实现Set的值不重复),所以转成map就可以了。
解决方法:转成map之后便可以加载

@PostMapping(value = "/flowList")public Object flowList(@RequestBody FlowableReq req) {log.info("查询待办流程{}", JSON.toJSONString(req));List<Task> list = flowService.todoList(req.getUserId());List<Map<String, Object>> listMap = new ArrayList<>();String[] ps = {"id","name"};list.forEach(task -> {//解决懒加载的对象无法在controller中返回的问题listMap.add(CommUtil.obj2map(task,ps));});log.info("查询待办流程成功{}", listMap.toString());return buildJsonWrapSuccess(listMap);}

工具类:CommUtil

package org.jxzx.baseframe.utils;import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;import org.springframework.beans.BeanUtils;public class CommUtil {/*** 把指定的复杂对象属性,按照指定的内容,封装到新的map中* @param source 目标对象* @param ps     需要封装到map中的属性* @return*/public static Map<String, Object> obj2map(Object source, String[] ps) {Map<String, Object> map = new HashMap<>();if (source == null)return null;if (ps == null || ps.length < 1) {return null;}for (String p : ps) {PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), p);if (sourcePd != null && sourcePd.getReadMethod() != null) {try {Method readMethod = sourcePd.getReadMethod();if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}Object value = readMethod.invoke(source, new Object[0]);map.put(p, value);} catch (Exception ex) {throw new RuntimeException("Could not copy properties from source to target",ex);}}}return map;}
}

Flowable教程相关推荐

  1. 流程引擎之Flowable简介

    背景 Flowable 是一个流行的轻量级的采用 Java 开发的业务流程引擎,通过 Flowable 流程引擎,我们可以部署遵循 BPMN2.0 协议的流程定义(一般为XML文件)文件,并能创建流程 ...

  2. flowable画图教程_flowable画图教程_Flowable 学习笔记

    一.Flowable 入门介绍 官网地址:https://www.flowable.org/ Flowable6.3中文教程:https://tkjohn.github.io/flowable-use ...

  3. flowable画图教程_JeeGit企业级快速开发平台-JeeSite4 Flowable入门教程

    注: 998元以下课程无咨询服务 该课程包含服务内容:299元含发票 在线课程观看权 购课后,教学资源联系长春叭姐QQ:3211247533 索要 教学内容 3.1 第一章 业务流程 BPM.工作流引 ...

  4. 若依(RuoYi-Vue)+Flowable工作流前后端整合教程

    此教程适合若依前后端分离项目,其他项目可以在扩展列表中进行查找. 近期公司里需要对很久以前的RuoYi-Vue前后端分离项目扩展出flowable的功能,当然这个重任也是落在了我的身上(不然也不会有这 ...

  5. [ Flowable ] 与modeler流程设计器整合教程

    Flowable 与 modeler 流程设计器整合方案 本教程基于Flowable 6.2.1 ,破解 flowable-idm的权限登录,整合SpringMVC实现maven动态导入jar包,期间 ...

  6. 全网最全面工作流引擎Flowable完整教程之多实例会签

    Flowable完整教程之多实例会签 前言 1.BladeX流程设计器 1.1.BladeX工作流设计 1.2.parallel多实例流程设计 1.3. BladeX多实例任务节点参数设置 2.部署测 ...

  7. Flowable 快速入门教程:Flowable 入门开发案例,结合流程设计器详细讲解

    Flowable 快速入门教程:Flowable 入门开发案例,结合流程设计器详细讲解 前言 流程设计器集成 整体流程图 流程节点说明 第一审核人节点:实际设置审核人 配置信息 说明 第二审核人:参数 ...

  8. Flowable 快速入门教程:前端展示流程图

    Flowable 快速入门教程:流程图展示 后端 前端 效果图 后端 这里流程图就不做高亮处理了 获取流程图 InputStream 将 InputStream 转为 byte[] 字节数组 对数组进 ...

  9. flowable画图教程_给初学者的RxJava2.0教程(七): Flowable

    作者博客http://www.jianshu.com/u/c50b715ccaeb前言上一节里我们学习了只使用Observable如何去解决上下游流速不均衡的问题,之所以学习这个是因为Observab ...

最新文章

  1. Terraform入门 - 3. 变更基础设施
  2. 又到校招季,来说说面试和实习
  3. 为什么一定要用MQ中间件
  4. core-js@2 core-js@3报错问题
  5. java优秀算法河内之塔_河内塔的Java程序
  6. 数据科学家数据分析师_使您的分析师和数据科学家在数据处理方面保持一致
  7. 郁金香汇编代码注入怎么写看雪_汇编语言入门五:流程控制(一)
  8. 【剑指offer】旋转数组的最小数字
  9. windows7系统iis安装不了应该怎么办
  10. 手机游戏修改客户端服务器,服务器 客户端手机游戏
  11. RabbitMQ系列(九)RabbitMQ进阶-Queue队列参数详解
  12. Talk预告 | 斯坦福大学石佳欣:无监督学习的未来-两条路径和统一视角
  13. 【转载】VS2019使用技巧
  14. 今日头条校招真题——头条校招
  15. 在PHP中如何获取用户的真实IP
  16. 洛谷——P7583 [COCI2012-2013#1] DOM(java实现)
  17. vue 大屏数字上下滚动
  18. 计算机专业选纽约大学还是南加州,美国TOP20计算机专业大学申请建议
  19. js实现椭圆运动轨迹
  20. 51单片机能否实现硬件仿真

热门文章

  1. STM32串口发16位数据
  2. 记录VITE Vue3开发需要的一些常用插件
  3. 动态网页技术的发展走向
  4. 根据/proc/partitions获取插入的U盘设备名称
  5. 数据库MongoDB启动方式(3种) - 方法总结篇
  6. RPKM与TPM值的区别
  7. pycharm配置conda虚拟环境
  8. Reac生命周期(简单易懂)
  9. 输入一个数组,求出这个数组中的逆序对的总数
  10. 李笑来泄露的50分钟谈话录音到底说了什么?