初探工作流引擎Flowable,改造Flowable流程编辑器使用自定义认证

11,131 阅读14分钟

前言

本文的目标,是在Spring (SpringBoot)项目中,集成Flowable 工作流引擎和Flowable流程编辑器。 通过流程编辑器编辑并发布流程,在Spring项目中使用。



Flowable 是一个基于Apache 2.0协议,开源,并且有着大量社区讨论与更新的工作流引擎。

春节疫情宅家的时候,刚发布了最新的6.5.0版本。本文中使用次新版本6.4.2。

使用工作流引擎,在处理复杂流程任务,例如订单、报价、采购处理、合同审核、请假申请时,能给各方人员带来好处。

工作流让开发人员不再需要特别关注流程流转,从而能更专注与业务实现,减少硬性编码,更利于维护和扩展。

工作流让流程应用使用人员,能更清晰地审视,管理流程,更灵活,快速地修改流程,从而另企业办公更加自动化与规范化,提升企业的管理效率,合规性与风控水平。


Flowable官方Github: github.com/flowable

官方网站: flowable.com/

注意,Flowable有开源与商用版的区别,商用的功能文档都要多得多,当然,它是要收费的。

Flowable开源社区版本在 flowable.com/open-source…

Flowable简介

主要组成部分

  • BPMN(Business Process Modeling Notation)引擎 - 业务流程模型引擎采用bpmn2.0标准的流程引擎。
  • CMN 决策引擎,基于 PMML/DMN标准的规则引擎,配合流程中的“决策任务”使用 。
  • CMMN 案例引擎,CMMN标准,与流程引擎相比,更适用于动态、自由、并行的情况。
  • Form 表单引擎,管理表单, 表单信息由json表达,可绑定到流程的节点中使用。

                                     


官方提供的4 个示例应用 

Flowable-UI-Modeler

模型编辑器,能编辑 流程 / 决策表 / 表单 编辑器 

Flowable-UI-Task 

操作与管理flowable 任务的UI 应用,用户可以通过它对Flowable任务进行 查看/接收/完成 等操作。

Flowbale-UI-Admin 

管理员权限的Flowable用户通过这个来查询和修改一些流程/规则/表单

Flowable-UI-IDM

用户等身份认证与权限管理,Flowable IDM的图形界面应用


要想简单快速地运行体验一把Flowable, 可以使用官方提供的All in One docker image

hub.docker.com/r/flowable/…




Spring项目与Flowable的集成

假设我们的集成是这样的:

现有一个Spring项目,它有自己的用户,业务等。它是一个单独进程,在下文中称作Spring项目。

Flowable UI Modeler 作为另一个单独的进程,下文中或称为Modeler。

两者通过Rest方式通信,Modeler为Spring项目提供流程建模服务,使用Spring项目提供的用户身份权限认证接口。Spring项目中集成Flowable 引擎核心,使用Modeler编辑发布过来的流程模型完成工作流。


第一步:创建启动SpringBoot项目,集成Flowable引擎核心

这里使用低码平台DaaS生成了一个简单的SpringBoot项目。

DaaS是成都双链科技(对,就是我公司)自主研发的低码开发平台,它可以帮助咨询人员快速生成可运行产品原型,让程序员更专注于业务,让产品开发迭代更加快速,规范,产业化。具体请见示例项目

github.com/doublechain…

以及客户端工具

github.com/doublechain…


这个项目暂时不带任何实际业务,只用于与Flowable集成测试,它具有以下3个功能:

1. 提供了一个账号+密码的登录认证接口给Flowable-UI-Modeler使用

DaaS生成的项目中,自带用户身份认证管理,对外提供账号密码登录接口大致如下,这个就是平时项目里的登录接口,发送用户名密码,登录成功返回用户信息:

HTTP POST:
http://localhost:8880/flowable/secUserManager/login/username/password/

Request:
{
   "username":"XXXXXX",
   "password":"XXXXXX"
}

Response:
{
	......
	"displayName": "张三",
	"id": "SU000001",
	"login": "User000001",
	"mobile": "139****0001",
	"email": "1000001@qq.com",
	"pwd": "**********",
	......
}


2. 引入了Flowable Engine核心,以使用通过Flowable-UI-Modeler编辑并发布过来的流程

在gradle中加入Flowable提供的SpringBoot Starter即可

compile 'org.flowable:flowable-spring-boot-starter:6.4.2'
3. 向Flowable-UI-Modeler提供流程定义发布接口

在gradle中加入Flowable-app-rest依赖

compile 'org.flowable:flowable-app-rest:6.4.2

在SpringBoot启动类并引入Flowable-app-rest模块中的org.flowable.app.rest.service.api.repository.AppDeploymentCollectionResource,向Flowable-UI-Modeler提供流程定义发布接口

@Import(org.flowable.app.rest.service.api.repository.AppDeploymentCollectionResource.class)

4. 去掉Flowable的Spring Security配置以免干扰Spring项目原来自己的安全策略

    PS: 实际上这里我去掉了所有Spring Security配置,单纯去掉Flowble的,只需要去掉FlowableSecurityAutoConfiguration

@SpringBootApplication(exclude = { SecurityAutoConfiguration.class,
		FlowableSecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class })

5. 启动应用

最终,Spring项目完整的gradle配置文件如下,请注意配置文件中都注释,有部分依赖是DaaS自带的依赖,并非Flowable必须的

buildscript {
    repositories {      
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE")
    }
}


apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group 'com.doublechaintech'
version '1.0.0'



[compileJava,compileTestJava,javadoc]*.options*.encoding = "UTF-8"
[compileJava,compileTestJava]*.options*.compilerArgs = ["-parameters"]

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

sourceSets {
    main {
    	output.resourcesDir = file('WEB-INF/classes')
        java.outputDir = file('WEB-INF/classes')
        java {   srcDirs =  srcFolders.split(",").toList();}
        resources {srcDirs =  srcFolders.split(",").toList(); }
    }
    test {
        java {
            srcDirs = ['WEB-INF/unittesting']
        }
        resources {
            srcDirs = ['WEB-INF/unittesting']
        }
    }
}

repositories {
    mavenLocal()
    maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    jcenter()
    mavenCentral()
}

dependencies {
	// DaaS项目自带依赖
    compile group: 'com.auth0', name: 'java-jwt', version: '3.4.0'
    compile group: 'com.aliyun', name: 'aliyun-java-sdk-core', version: '3.5.1'
    compile group: 'com.aliyun', name: 'aliyun-java-sdk-dysmsapi', version: '1.0.0'
    compile group: 'com.aliyun', name: 'aliyun-java-sdk-sts', version: '3.0.0'
    compile group: 'com.aliyun.mns', name: 'aliyun-sdk-mns', version: '1.1.8'
    compile group: 'com.aliyun.oss', name: 'aliyun-sdk-oss', version: '2.8.3'
    compile group: 'com.github.binarywang', name: 'weixin-java-miniapp', version: '3.5.0'
    compile group: 'com.github.binarywang', name: 'weixin-java-pay', version: '3.5.0'
    compile group: 'com.github.binarywang', name: 'weixin-java-cp', version: '3.5.0'
    compile group: 'javax.mail', name: 'mail', version: '1.4.7'
    compile group: 'mysql', name: 'mysql-connector-java'
    compile group: 'com.h2database', name: 'h2', version: '1.4.200'
    compile group: 'org.apache.kafka', name: 'kafka-clients', version: '1.1.0'
    compile group: 'com.zaxxer', name: 'HikariCP', version: '3.3.1'
    compile group: 'org.elasticsearch.client', name: 'elasticsearch-rest-high-level-client', version: '5.6.3'
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-jdbc")
    compile 'com.qiniu:qiniu-java-sdk:7.2.+'
    compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.14.4'
    compile group: 'redis.clients', name: 'jedis', version: '2.9.0'
  	compile group: 'org.apache.poi', name: 'poi-ooxml', version: '4.0.1'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    
    // Fowable相关依赖
    compile 'org.flowable:flowable-spring-boot-starter:6.4.2'
	compile ('org.flowable:flowable-app-rest:6.4.2'){
		 exclude group: 'org.slf4j'
	}
	compile group: 'org.flowable', name: 'flowable-form-model', version: '6.4.2'
	
    annotationProcessor 'org.projectlombok:lombok:1.18.2'
    compileOnly 'org.projectlombok:lombok:1.18.2'
}

完整启动类如下,

package com.skynet.bootstrap;
import org.flowable.app.rest.AppRestResponseFactory;
import org.flowable.spring.boot.FlowableSecurityAutoConfiguration;
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.elasticsearch.rest.RestClientAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import com.doublechain.flowable.CustomUCInvocationServlet;

@Import(org.flowable.app.rest.service.api.repository.AppDeploymentCollectionResource.class)
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, RestClientAutoConfiguration.class, KafkaAutoConfiguration.class, SecurityAutoConfiguration.class,
		FlowableSecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class })
@ImportResource(locations = { "classpath:/META-INF/spring.xml", "classpath:/META-INF/online-system.xml" })
@ServletComponentScan(basePackageClasses = { CustomUCInvocationServlet.class })
public class AppEntrance {
	public static void main(String[] args) {
		SpringApplication.run(AppEntrance.class, args);
	}

	@Bean
	public AppRestResponseFactory restResponseFactory() {
		return new AppRestResponseFactory();
	}
}

启动之后,可以从日志看到,Flowable-spring-boot-starter利用Liquibase, 自动创建了Flowable的相关数据库表。至此,我们就启动了一个引入Flowable的SpringBoot项目


第二步:启动Flowable UI Modeler

这里,我们通过引入Maven Repository里现成的依赖,来启动一个Flowable-UI-Modeler

1. 通过Spring Boot Initializer start.spring.io/来初始化一个Spring Boot项目,啥依赖都不加,生成一个简单SpringBoot项目


2. 在build.gradle中加入Flowable-UI-Modeler的依赖

implementation 'org.flowable:flowable-ui-modeler-app:6.4.2'

PS:吐槽一下,这个应该弄个Starter啊


3. 从Flowable-UI-Modeler源码中拷贝配置文件内容,加入到我们的配置文件里(application.properties),完整内容如下,其中重要的部分请参见中文注释

# JDBC Driver class
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# JDBC连接字符串
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/flowable_ui_modeler?characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true

# 数据库账号密码
spring.datasource.username=root
spring.datasource.password=hahahaha

# 允许覆盖Bean定义
spring.main.allow-bean-definition-overriding=true

spring.mvc.static-path-pattern=/**
# SpringMVC静态资源路径
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,\
classpath:/static/,classpath:/public/,classpath:/WEB-INF/classes/static/

server.servlet.context-path=/flowable-modeler

management.endpoints.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-modeler
#
# SECURITY
#
spring.security.filter.dispatcher-types=REQUEST,FORWARD,ASYNC
spring.liquibase.enabled=false
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}

# 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=
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=5
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

# The maximum file upload limit. Set to -1 to set to 'no limit'. Expressed in bytes
spring.servlet.multipart.max-file-size=10MB

flowable.common.app.idm-url=http://localhost:8880/flowable-idm
flowable.common.app.idm-admin.user=SU000001
flowable.common.app.idm-admin.password=admin123
flowable.modeler.app.deployment-api-url=http://localhost:8880/flowable/dispatch

# Rest API
flowable.modeler.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

server.port=8888

# 认证接口
auth.url=http://localhost:8880/flowable/secUserManager/login/username/password/


4. 改造Spring Security部分,使用自己SpringBoot项目提供的认证接口


根据Flowable官方说明,启动Flowable UI Modeler,或者说启动任何一个Flowable 应用时,必须同时启动Flowable IDM (只是Flowable IDM 不是 Flowable-UI-IDM. Flowable-UI-IDM是Flowable IDM + 图形界面 ),为Flowable应用提供身份认证服务。

Flowable UI Modeler中,关于安全的配置在配置类

org.flowable.ui.modeler.conf.SecurityConfiguration

当同时启动Flowale-UI-Modeler和Flowable IDM时,关于身份和权限认证这一块儿,大概是这样的:


从上图,与SecurityConfiguration中,可以看到,有以下几个地方需要改动:

在最前面有一个FlowableCookieFilter, 它做的事情用伪代码大概表现为这样的:

if(不允许跳过){
    if( 存在名为FLOWABLE_REMEMBER_ME的Cookie ){
        以其值作参数向IDM查询获得一个RemoteToken
        if(RemoteToken!=null){
            用RemoteToken向IDM查询用户是否存在
            if(用户存在){
                //在SpringSecurity上下文中设置一个 RememberMeAuthenticationToken
                //之后走SpringSecurity
                SecurityContextHolder.getContext().setAuthentication(RememberMeAuthenticationToken);
            }
        }
    }else{
        跳转到IDM 的登录页面引导用户登录
    }
}

首先,我不希望去检查啥flowable cookie, 我甚至都不需要cookie, 因此,这个filter需要通过覆盖去掉

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean(RemoteIdmService remoteIdmService,
		FlowableCommonAppProperties properties) {
	return new FlowableCookieFilterRegistrationBean(remoteIdmService, properties) {
		@Override
		protected void initializeFilter() {
			if (getFilter() == null) {
				FlowableCookieFilter flowableCookieFilter = new FlowableCookieFilter(remoteIdmService, properties) {
					@Override
					protected boolean skipAuthenticationCheck(HttpServletRequest request) {
						return true;
					}
				};
				setFilter(flowableCookieFilter);
			}
		}
	};
}


Flowable-UI-Modeler同样开启了SpringSecurity。在配置类

org.flowable.ui.modeler.conf.SecurityConfiguration.configureGlobal(AuthenticationManagerBuilder)

中注册了

org.flowable.ui.modeler.security.RemoteIdmAuthenticationProvider

其中又使用了

org.flowable.ui.common.service.idm.RemoteIdmService

通过Rest的方式调用Flowable IDM接口,最终认证的用户结果是一个

org.flowable.ui.common.security.FlowableAppUser

对象

这里为了使用我们自己的SpringBoot项目提供的登录认证接口,需要自己写一个AuthenticationProvider来替换Flowable的RemoteIdmAuthenticationProvider,实现调用远端认证接口,然后把结果组装成一个FlowableAppUser,让它接着往下走

package com.doublechain.flowable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.flowable.ui.common.model.RemoteUser;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.common.security.FlowableAppUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class CustomFlowableAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	@Getter
	@Setter
	@Value("${auth.url}")
	//http://localhost:8880/flowable/secUserManager/login/username/password/
	private String authUrl;
		public CustomFlowableAuthenticationProvider() {
			super();
		}

		@Override
	    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
	    	String password = authentication.getCredentials().toString();
	        return getRemoteUser(username,password);
	    }

	    protected FlowableAppUser getRemoteUser(String login,String password) {
			FormBody.Builder builder = new FormBody.Builder();
			builder.add("username", login);
			builder.add("password", password);
			Request request = new Request.Builder().url(getAuthUrl()).post(builder.build()).build();
			Response resp=null;
			try {
				 resp = new OkHttpClient().newCall(request).execute();
				String result = resp.body().string();
				Map resultObject = new ObjectMapper().readValue(result, Map.class);
				RemoteUser remoteUser = new RemoteUser();
				remoteUser.setId((String) resultObject.get("id"));
				remoteUser.setPassword((String) resultObject.get("pwd"));
				remoteUser.setFirstName(remoteUser.getId());
				List<SimpleGrantedAuthority> auths = new ArrayList<>();
				auths.add(new SimpleGrantedAuthority(DefaultPrivileges.ACCESS_MODELER));
				return new FlowableAppUser(remoteUser, remoteUser.getId(),auths );
			} catch (IOException e) {
				e.printStackTrace();
			}finally {
				Optional.ofNullable(resp).ifPresent(Response::close);
			}
			return null;
		}

		@Override
		protected void additionalAuthenticationChecks(UserDetails userDetails,
				UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
			//TODO
		}
}

把自定义的auth provider注册到Spring Security中

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnProperty(prefix = "flowable.idm.ldap", name = "enabled", havingValue = "false", matchIfMissing = true)
public AuthenticationProvider authenticationProvider() {
	return new CustomFlowableAuthenticationProvider();
}

@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomFormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
	http.formLogin().and().authenticationProvider(authenticationProvider()).authorizeRequests().anyRequest().authenticated().and().csrf().disable();
    }
}

最终,完整的Flowable-UI-Modeler启动类

package com.doublechain.flowable;

import javax.servlet.http.HttpServletRequest;
import org.flowable.ui.common.filter.FlowableCookieFilter;
import org.flowable.ui.common.filter.FlowableCookieFilterRegistrationBean;
import org.flowable.ui.common.properties.FlowableCommonAppProperties;
import org.flowable.ui.common.service.idm.RemoteIdmService;
import org.flowable.ui.modeler.conf.ApplicationConfiguration;
import org.flowable.ui.modeler.servlet.AppDispatcherServletConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Import({ ApplicationConfiguration.class, AppDispatcherServletConfiguration.class })

@SpringBootApplication
@EnableWebSecurity
public class FlowableApplication extends SpringBootServletInitializer {
	public static void main(String[] args) {

		SpringApplication.run(FlowableApplication.class, args);
	}

	/**
	 * 重写 Flowable 的 FlowableCookieFilterRegistrationBean,跳过cookie认证
	 *
	 * @param remoteIdmService
	 * @param properties
	 * @return
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean(RemoteIdmService remoteIdmService, FlowableCommonAppProperties properties) {
		return new FlowableCookieFilterRegistrationBean(remoteIdmService, properties) {
			@Override
			protected void initializeFilter() {
				if (getFilter() == null) {
					FlowableCookieFilter flowableCookieFilter = new FlowableCookieFilter(remoteIdmService, properties) {
						@Override
						protected boolean skipAuthenticationCheck(HttpServletRequest request) {
							return true;
						}
					};
					setFilter(flowableCookieFilter);
				}
			}
		};
	}

	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	@ConditionalOnProperty(prefix = "flowable.idm.ldap", name = "enabled", havingValue = "false", matchIfMissing = true)
	public AuthenticationProvider authenticationProvider() {
		return new CustomFlowableAuthenticationProvider();
	}

	@EnableWebSecurity
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public class CustomFormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http.formLogin().and().authenticationProvider(authenticationProvider()).authorizeRequests().anyRequest().authenticated().and().csrf().disable();
		}
	}
}

5. 启动并验证

gradle bootRun, 把SpringBoot项目和改造的Flowable-UI-Modeler都启起来,

访问Flowable-UI-Modeler, 发现被带到了SpringSecurity默认登录页


输入用户名密码,就进去啦.



第三步:编辑流程,发布使用

如何在Flowable-UI-Modeler中编辑一个流程,这个还是以官方文档为参考最好。而且Flowable 编辑器设计总体来说也比较简明易懂,汉化不明白的地方,可以直接看看英文。

从Flowable-UI-Modeler发布流程定义到SpringBoot应用其实十分简单。

在SpringBoot应用中,我们已经通过引入

org.flowable.app.rest.service.api.repository.AppDeploymentCollectionResource

获得了发布接口。接下来只需在Flowable-UI-Modeler的配置文件中配置接口地址就可以了

例如

flowable.modeler.app.deployment-api-url=http://localhost:8880/flowable

 唯一需要注意一点的是,这里只是配一个前缀而不是全url

可以从源码

org.flowable.ui.modeler.service.AppDefinitionPublishService.deployZipArtifact(String, byte[], String, String)

中看到,Flowable-UI-Modeler在这个后面自己拼接了URI....


总结

我们创建了一个SpringBoot应用,一个Flowable-UI-Modeler应用

在SpringBoot应用中只引入Flowable SpringBoot starter以使用Flowable工作流引擎,引入了一个Flowable Rest中包含的Rest Resource为Flowable-UI-Moldeler提供流程定义发布接口

在Flowable-UI-Modeler中,我们重写了Spring Security部分,让Flowable-UI-Modeler使用自定义都用户身份和权限验证

我们在Flowable-UI-Modeler中编辑了流程定义,发布到了SpringBoot应用。

百度一搜Flowable Modeler集成,大量的文章都是拿着Flowable源码直接改。

建议大家还是尽量override.





 Troubleshooting & QA

 1  日志组件冲突:

logging SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/Users/jarryzhou/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.12.1/14973e22497adaf0196d481fb99c5dc2a0b58d41/log4j-slf4j-impl-2.12.1.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/Users/jarryzhou/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

由于SpringBoot和Flowable一个用logback一个用log4j, 通过Starter引入他们的时候,这里会产生冲突,有2种解决方法,

一种是在其中某一个中忽略日志依赖,比如

compile 'org.flowable:flowable-spring-boot-starter:6.4.2'compile ('org.flowable:flowable-ui-modeler-app:6.4.2'){        exclude group: 'org.slf4j'}

另一种是,使用implementation替代compile, 这样就各用各的。。

compile("org.springframework.boot:spring-boot-starter-web")
implementation 'org.flowable:flowable-ui-modeler-app:6.4.2'

。。。。

2 启动时不自动建表

有可能是Liquibase禁用了,看配置文件是不是有

spring.liquibase.enabled=false

得改成true.

也有可能像我一样,mysql连接字符串加上nullCatalogMeansCurrent=true就好啦,

关于nullCatalogMeansCurrent,就请自行百度了。


3 插入中文数据报错

java.sql.SQLException: Incorrect string value: '\xE6\xB5\x8B\xE8\xAF\x95' for column 'name' at row 1at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.19.jar:8.0.19]at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.19.jar:8.0.19]at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.19.jar:8.0.19]at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.19.jar:8.0.19]at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:370) ~[mysql-connector-java-8.0.19.jar:8.0.19]at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) ~[HikariCP-3.4.2.jar:na]


建表的字符集不对,可以改flowable表的默认字符集,以mysql 为例子:

SET FOREIGN_KEY_CHECKS=0;
alter table ACT_APP_APPDEF convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_APP_DATABASECHANGELOG convert to character set utf8mb4 collate utf8mb4_bin;      
alter table ACT_APP_DATABASECHANGELOGLOCK convert to character set utf8mb4 collate utf8mb4_bin;  
alter table ACT_APP_DEPLOYMENT convert to character set utf8mb4 collate utf8mb4_bin;             
alter table ACT_APP_DEPLOYMENT_RESOURCE convert to character set utf8mb4 collate utf8mb4_bin;    
alter table ACT_CMMN_CASEDEF convert to character set utf8mb4 collate utf8mb4_bin;               
alter table ACT_CMMN_DATABASECHANGELOG convert to character set utf8mb4 collate utf8mb4_bin;     
alter table ACT_CMMN_DATABASECHANGELOGLOCK convert to character set utf8mb4 collate utf8mb4_bin; 
alter table ACT_CMMN_DEPLOYMENT convert to character set utf8mb4 collate utf8mb4_bin;            
alter table ACT_CMMN_DEPLOYMENT_RESOURCE convert to character set utf8mb4 collate utf8mb4_bin;   
alter table ACT_CMMN_HI_CASE_INST convert to character set utf8mb4 collate utf8mb4_bin;          
alter table ACT_CMMN_HI_MIL_INST convert to character set utf8mb4 collate utf8mb4_bin;           
alter table ACT_CMMN_HI_PLAN_ITEM_INST convert to character set utf8mb4 collate utf8mb4_bin;     
alter table ACT_CMMN_RU_CASE_INST convert to character set utf8mb4 collate utf8mb4_bin;          
alter table ACT_CMMN_RU_MIL_INST convert to character set utf8mb4 collate utf8mb4_bin;           
alter table ACT_CMMN_RU_PLAN_ITEM_INST convert to character set utf8mb4 collate utf8mb4_bin;     
alter table ACT_CMMN_RU_SENTRY_PART_INST convert to character set utf8mb4 collate utf8mb4_bin;   
alter table ACT_CO_CONTENT_ITEM convert to character set utf8mb4 collate utf8mb4_bin;            
alter table ACT_CO_DATABASECHANGELOG convert to character set utf8mb4 collate utf8mb4_bin;       
alter table ACT_CO_DATABASECHANGELOGLOCK convert to character set utf8mb4 collate utf8mb4_bin;   
      
alter table ACT_DE_DATABASECHANGELOGLOCK convert to character set utf8mb4 collate utf8mb4_bin;   
alter table ACT_DE_MODEL convert to character set utf8mb4 collate utf8mb4_bin;                   
alter table ACT_DE_MODEL_HISTORY convert to character set utf8mb4 collate utf8mb4_bin;           
alter table ACT_DE_MODEL_RELATION convert to character set utf8mb4 collate utf8mb4_bin;          
alter table ACT_DMN_DATABASECHANGELOG convert to character set utf8mb4 collate utf8mb4_bin;      
alter table ACT_DMN_DATABASECHANGELOGLOCK convert to character set utf8mb4 collate utf8mb4_bin;  
alter table ACT_DMN_DECISION_TABLE convert to character set utf8mb4 collate utf8mb4_bin;         
alter table ACT_DMN_DEPLOYMENT convert to character set utf8mb4 collate utf8mb4_bin;             
alter table ACT_DMN_DEPLOYMENT_RESOURCE convert to character set utf8mb4 collate utf8mb4_bin;    
alter table ACT_DMN_HI_DECISION_EXECUTION convert to character set utf8mb4 collate utf8mb4_bin;  
alter table ACT_EVT_LOG convert to character set utf8mb4 collate utf8mb4_bin;                    
alter table ACT_FO_DATABASECHANGELOG convert to character set utf8mb4 collate utf8mb4_bin;       
alter table ACT_FO_DATABASECHANGELOGLOCK convert to character set utf8mb4 collate utf8mb4_bin;   
alter table ACT_FO_FORM_DEFINITION convert to character set utf8mb4 collate utf8mb4_bin;         
alter table ACT_FO_FORM_DEPLOYMENT convert to character set utf8mb4 collate utf8mb4_bin;         
alter table ACT_FO_FORM_INSTANCE convert to character set utf8mb4 collate utf8mb4_bin;           
alter table ACT_FO_FORM_RESOURCE convert to character set utf8mb4 collate utf8mb4_bin;           
alter table ACT_GE_BYTEARRAY convert to character set utf8mb4 collate utf8mb4_bin;               
alter table ACT_GE_PROPERTY convert to character set utf8mb4 collate utf8mb4_bin;                
alter table ACT_HI_ACTINST convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_HI_ATTACHMENT convert to character set utf8mb4 collate utf8mb4_bin;              
alter table ACT_HI_COMMENT convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_HI_DETAIL convert to character set utf8mb4 collate utf8mb4_bin;                  
alter table ACT_HI_ENTITYLINK convert to character set utf8mb4 collate utf8mb4_bin;              
alter table ACT_HI_IDENTITYLINK convert to character set utf8mb4 collate utf8mb4_bin;            
alter table ACT_HI_PROCINST convert to character set utf8mb4 collate utf8mb4_bin;                
alter table ACT_HI_TASKINST convert to character set utf8mb4 collate utf8mb4_bin;                
alter table ACT_HI_TSK_LOG convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_HI_VARINST convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_ID_BYTEARRAY convert to character set utf8mb4 collate utf8mb4_bin;               
alter table ACT_ID_GROUP convert to character set utf8mb4 collate utf8mb4_bin;                   
alter table ACT_ID_INFO convert to character set utf8mb4 collate utf8mb4_bin;                    
alter table ACT_ID_MEMBERSHIP convert to character set utf8mb4 collate utf8mb4_bin;              
alter table ACT_ID_PRIV convert to character set utf8mb4 collate utf8mb4_bin;                    
alter table ACT_ID_PRIV_MAPPING convert to character set utf8mb4 collate utf8mb4_bin;            
alter table ACT_ID_PROPERTY convert to character set utf8mb4 collate utf8mb4_bin;                
alter table ACT_ID_TOKEN convert to character set utf8mb4 collate utf8mb4_bin;                   
alter table ACT_ID_USER convert to character set utf8mb4 collate utf8mb4_bin;                    
alter table ACT_PROCDEF_INFO convert to character set utf8mb4 collate utf8mb4_bin;               
alter table ACT_RE_DEPLOYMENT convert to character set utf8mb4 collate utf8mb4_bin;              
alter table ACT_RE_MODEL convert to character set utf8mb4 collate utf8mb4_bin;                   
alter table ACT_RE_PROCDEF convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_RU_ACTINST convert to character set utf8mb4 collate utf8mb4_bin;                 
alter table ACT_RU_DEADLETTER_JOB convert to character set utf8mb4 collate utf8mb4_bin;          
alter table ACT_RU_ENTITYLINK convert to character set utf8mb4 collate utf8mb4_bin;              
alter table ACT_RU_EVENT_SUBSCR convert to character set utf8mb4 collate utf8mb4_bin;            
alter table ACT_RU_EXECUTION convert to character set utf8mb4 collate utf8mb4_bin;               
alter table ACT_RU_HISTORY_JOB convert to character set utf8mb4 collate utf8mb4_bin;             
alter table ACT_RU_IDENTITYLINK convert to character set utf8mb4 collate utf8mb4_bin;            
alter table ACT_RU_JOB convert to character set utf8mb4 collate utf8mb4_bin;                     
alter table ACT_RU_SUSPENDED_JOB convert to character set utf8mb4 collate utf8mb4_bin;           
alter table ACT_RU_TASK convert to character set utf8mb4 collate utf8mb4_bin;                    
alter table ACT_RU_TIMER_JOB convert to character set utf8mb4 collate utf8mb4_bin;               
alter table ACT_RU_VARIABLE convert to character set utf8mb4 collate utf8mb4_bin;