一、Flowable-Modeler功能
https://gitee.com/lwj/flow-modeler-sduty/tree/master/src/main
提供可视化编辑器,编辑BPMN流程,编辑CASE模型,编辑Form表单,编辑App应用,编辑决策表
提供可视化参数配置:每个流程可以配置详细的参数设置,按照流程对应的规范来设计。
提供导入导出功能:方便将流程结果导入到其他应用程序
在我们实际项目中,我们的流程配置和表单都是在一个系统中操作的,不可能在flowable的war包上做流程配置。
所以集成modeler是flowable使用的开端。
二、源码下载与编译
-
源码下载
下载地址:
https://github.com/flowable/flowable-engine
-
导入idea编译
编译结果:
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Flowable 6.5.0-SNAPSHOT:
[INFO]
[INFO] Flowable ........................................... SUCCESS [ 0.326 s]
[INFO] Flowable - BPMN Model .............................. SUCCESS [ 0.767 s]
[INFO] Flowable - Process Validation ...................... SUCCESS [ 0.070 s]
[INFO] Flowable - BPMN Layout ............................. SUCCESS [ 0.106 s]
[INFO] Flowable - Image Generator ......................... SUCCESS [ 0.102 s]
[INFO] Flowable - Engine Common API ....................... SUCCESS [ 0.059 s]
[INFO] Flowable - Variable Service API .................... SUCCESS [ 0.036 s]
[INFO] Flowable - Engine Common ........................... SUCCESS [ 0.455 s]
[INFO] Flowable - BPMN Converter .......................... SUCCESS [ 0.143 s]
[INFO] Flowable - Entity Link Service API ................. SUCCESS [ 0.021 s]
[INFO] Flowable - Entity Link Service ..................... SUCCESS [ 0.163 s]
[INFO] Flowable - Variable Service ........................ SUCCESS [ 0.198 s]
[INFO] Flowable - Identity Link Service API ............... SUCCESS [ 0.028 s]
[INFO] Flowable - Identity Link Service ................... SUCCESS [ 0.118 s]
[INFO] Flowable - Event Subscription Service API .......... SUCCESS [ 0.028 s]
[INFO] Flowable - Event Subscription Service .............. SUCCESS [ 0.127 s]
[INFO] Flowable - Task Service API ........................ SUCCESS [ 0.030 s]
[INFO] Flowable - IDM API ................................. SUCCESS [ 0.023 s]
[INFO] Flowable - Task Service ............................ SUCCESS [ 0.169 s]
[INFO] Flowable - Job Service API ......................... SUCCESS [ 0.023 s]
[INFO] Flowable - Job Service ............................. SUCCESS [ 0.179 s]
[INFO] Flowable Job Spring Service ........................ SUCCESS [ 0.141 s]
[INFO] Flowable - Batch Service API ....................... SUCCESS [ 0.017 s]
[INFO] Flowable - Batch Service ........................... SUCCESS [ 0.124 s]
[INFO] Flowable - IDM Engine .............................. SUCCESS [ 0.229 s]
[INFO] flowable-idm-engine-configurator ................... SUCCESS [ 0.108 s]
[INFO] Flowable - Form API ................................ SUCCESS [ 0.023 s]
[INFO] Flowable - Form Model .............................. SUCCESS [ 0.024 s]
[INFO] flowable-form-json-converter ....................... SUCCESS [ 0.037 s]
[INFO] Flowable - Form Engine ............................. SUCCESS [ 0.211 s]
[INFO] Flowable - CMMN Model .............................. SUCCESS [ 0.062 s]
[INFO] Flowable - DMN Model ............................... SUCCESS [ 0.024 s]
[INFO] Flowable - DMN API ................................. SUCCESS [ 0.058 s]
[INFO] Flowable - CMMN API ................................ SUCCESS [ 0.118 s]
[INFO] Flowable - Content API ............................. SUCCESS [ 0.041 s]
[INFO] Flowable - Engine .................................. SUCCESS [ 1.188 s]
[INFO] Flowable - Form Engine Configurator ................ SUCCESS [ 0.384 s]
[INFO] Flowable - CMMN Converter .......................... SUCCESS [ 0.060 s]
[INFO] Flowable - CMMN Image Generator .................... SUCCESS [ 0.041 s]
[INFO] Flowable - CMMN Engine ............................. SUCCESS [ 0.415 s]
[INFO] Flowable - CMMN Engine Configurator ................ SUCCESS [ 0.477 s]
[INFO] Flowable - App Engine API .......................... SUCCESS [ 0.020 s]
[INFO] Flowable - App Engine .............................. SUCCESS [ 0.168 s]
[INFO] flowable-spring-security ........................... SUCCESS [ 0.045 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.645 s
[INFO] Finished at: 2019-10-20T23:21:14+08:00
[INFO] ------------------------------------------------------------------------
三、Modeler集成
1. 拷贝全都静态资源到项目中resources
2. Maven依赖
由于flowable-modeler的流程设计器页面很多操作会访问后台接口,所以在这里导入依赖文件。
org.springframework.boot
spring-boot-starter-web
org.hibernate.validator
hibernate-validator
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-data-jdbc
com.h2database
h2
com.baomidou
mybatis-plus-boot-starter
3.1.2
org.flowable
flowable-ui-common
6.4.2
org.flowable
flowable-ui-modeler-conf
6.4.2
org.flowable
flowable-ui-modeler-rest
6.4.2
org.flowable
flowable-ui-modeler-logic
6.4.2
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
3. 配置文件
配置文件:application.properties
server.port=8002
server.servlet.context-path=/flowable-modeler
management.endpoints.jmx.unique-names=true
# 这是强制使用JDK代理而不是使用CGLIB所必需的。
spring.aop.proxy-target-class=false
spring.aop.auto=false
spring.application.name=flowable-ui-modeler
# 安全配置
spring.security.filter.dispatcher-types=REQUEST,FORWARD,ASYNC
spring.liquibase.enabled=false
# 必须指定用于生成对象名的默认域。否则,当多个spring引导应用程序在同一个servlet容器中启动时
# 所有这些都将使用相同的名称创建(例如com.zaxxer.hikari:name=datasource,type=hikaridatasource)
spring.jmx.default-domain=${spring.application.name}
# 健康检查
# 将所有执行器端点暴露在Web上它们是公开的,但只有经过身份验证的用户才能看到/info和/health
# abd具有access admin的用户才能看到其他用户
management.endpoints.web.exposure.include=*
# 只有在授权用户时才应显示完整的运行状况详细信息
management.endpoint.health.show-details=when_authorized
# 只有具有角色access admin的用户才能访问完整的运行状况详细信息
management.endpoint.health.roles=access-admin
# 数据库 默认H2数据库
spring.datasource.username=flowable
spring.datasource.password=flowable
# 数据库连接池
spring.datasource.hikari.maxLifetime=600000
# 5 minutes
spring.datasource.hikari.idleTimeout=300000
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=50
# 大文件上传限制。设置为-1可设置为“无限制”。以字节表示
spring.servlet.multipart.max-file-size=10MB
配置文件:flowable-default.properties
# spring在角色前面加上role。然而,flowable还没有这个概念,所以我们需要用空字符串覆盖它。
flowable.common.app.role-prefix=
flowable.common.app.idm-url=http://localhost:8002/flowable-idm
flowable.common.app.idm-admin.user=admin
flowable.common.app.idm-admin.password=test
flowable.modeler.app.deployment-api-url=http://localhost:8002/flowable-task/app-api
# Rest API
flowable.modeler.app.rest-enabled=true
flowable.rest.app.authentication-mode=verify-privilege
配置文件:version.properties
type=modeler
version.major=6
version.minor=4
version.revision=2
version.edition=Flowable
4. 创建流程模型包
创建包:org.flowable.ui
在java中创建包名:org.flowable.ui
5. 去除认证
1)创建org.flowable.ui.common.rest.idm.remote包,添加类:
import org.flowable.ui.common.model.UserRepresentation;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.common.service.exception.NotFoundException;
import org.flowable.ui.common.service.idm.RemoteIdmService;
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;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/app")
public class RemoteAccountResource {
@Autowired
private RemoteIdmService remoteIdmService;
/**
* GET /rest/account -> get the current user.
*/
@RequestMapping(value = "/rest/account", method = RequestMethod.GET, produces = "application/json")
public UserRepresentation getAccount() {
// UserRepresentation userRepresentation = null;
// String currentUserId = SecurityUtils.getCurrentUserId();
// if (currentUserId != null) {
// RemoteUser remoteUser = remoteIdmService.getUser(currentUserId);
// if (remoteUser != null) {
// userRepresentation = new UserRepresentation(remoteUser);
//
// if (remoteUser.getGroups() != null && remoteUser.getGroups().size() > 0) {
// List groups = new ArrayList<>();
// for (RemoteGroup remoteGroup : remoteUser.getGroups()) {
// groups.add(new GroupRepresentation(remoteGroup));
// }
// userRepresentation.setGroups(groups);
// }
//
// if (remoteUser.getPrivileges() != null && remoteUser.getPrivileges().size() > 0) {
// userRepresentation.setPrivileges(remoteUser.getPrivileges());
// }
//
// }
// }
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setFirstName("admin");
userRepresentation.setLastName("admin");
userRepresentation.setFullName("admin");
userRepresentation.setId("admin");
List 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);
userRepresentation.setPrivileges(pris);
if (userRepresentation != null) {
return userRepresentation;
} else {
throw new NotFoundException();
}
}
}
2)创建包: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;
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;
}
// User user = null;
// FlowableAppUser appUser = getCurrentFlowableAppUser();
// if (appUser != null) {
// user = appUser.getUserObject();
// }
RemoteUser user = new RemoteUser();
// FlowableAppUser appUser = getCurrentFlowableAppUser();
// if (appUser != null) {
// user = appUser.getUserObject();
// }
user.setId("admin");
user.setDisplayName("admin");
user.setFirstName("admin");
user.setLastName("admin");
user.setEmail("[email protected]");
user.setPassword("test");
List 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;
}
}
3)创建包:org.flowable.ui.modeler.conf 添加安全配置类:
import org.flowable.ui.common.properties.FlowableRestAppProperties;
import org.flowable.ui.common.security.ActuatorRequestMatcher;
import org.flowable.ui.common.security.ClearFlowableCookieLogoutHandler;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.modeler.properties.FlowableModelerAppProperties;
import org.flowable.ui.modeler.security.AjaxLogoutSuccessHandler;
import org.flowable.ui.modeler.security.RemoteIdmAuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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 org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
/**
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(org.flowable.ui.modeler.conf.SecurityConfiguration.class);
public static final String REST_ENDPOINTS_PREFIX = "/app/rest";
@Autowired
protected RemoteIdmAuthenticationProvider authenticationProvider;
// @Bean
// public FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean(RemoteIdmService remoteIdmService, FlowableCommonAppProperties properties) {
// FlowableCookieFilterRegistrationBean filter = new FlowableCookieFilterRegistrationBean(remoteIdmService, properties);
// filter.addUrlPatterns("/app/*");
// filter.setRequiredPrivileges(Collections.singletonList(DefaultPrivileges.ACCESS_MODELER));
// return filter;
// }
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
// Default auth (database backed)
try {
auth.authenticationProvider(authenticationProvider);
} catch (Exception e) {
LOGGER.error("Could not configure authentication mechanism:", e);
}
}
@Configuration
@Order(10)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// @Autowired
// protected FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean;
@Autowired
protected AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// .addFilterBefore(flowableCookieFilterRegistrationBean.getFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()
.logoutUrl("/app/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.addLogoutHandler(new ClearFlowableCookieLogoutHandler())
.and()
.csrf()
.disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
// .antMatchers(REST_ENDPOINTS_PREFIX + "/**").hasAuthority(DefaultPrivileges.ACCESS_MODELER);
.antMatchers(REST_ENDPOINTS_PREFIX + "/**").permitAll();
}
}
//
// BASIC AUTH
//
@Configuration
@Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected final FlowableRestAppProperties restAppProperties;
protected final FlowableModelerAppProperties modelerAppProperties;
public ApiWebSecurityConfigurationAdapter(FlowableRestAppProperties restAppProperties,
FlowableModelerAppProperties modelerAppProperties) {
this.restAppProperties = restAppProperties;
this.modelerAppProperties = modelerAppProperties;
}
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable();
http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").permitAll();
// if (modelerAppProperties.isRestEnabled()) {
//
// if (restAppProperties.isVerifyRestApiPrivilege()) {
// http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").hasAuthority(DefaultPrivileges.ACCESS_REST_API).and().httpBasic();
// } else {
// http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").authenticated().and().httpBasic();
//
// }
//
// } else {
// http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").denyAll();
//
// }
}
}
//
// Actuator
//
@ConditionalOnClass(EndpointRequest.class)
@Configuration
@Order(5) // Actuator configuration should kick in before the Form Login there should always be http basic for the endpoints
public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable();
http
.requestMatcher(new ActuatorRequestMatcher())
.authorizeRequests()
.requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).authenticated()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAnyAuthority(DefaultPrivileges.ACCESS_ADMIN)
.and().httpBasic();
}
}
}
6. 启动类配置
修改原有启动类:包名com.xtsz.modeler
package com.xtsz.modeler;
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.context.annotation.Import;
/**
* 导入配置
*/
@Import({
ApplicationConfiguration.class,
AppDispatcherServletConfiguration.class
})
@SpringBootApplication
public class ModelerApplication {
public static void main(String[] args) {
SpringApplication.run(ModelerApplication.class, args);
}
}
7. 流程模型汉化
拷贝文件到resources目录:
四、启动测试
-
请求地址:http://localhost:8002/flowable-modeler/
-
创建流程
五、常见问题
- Cannot convert value '2019-10-22 08:09:24.000000' from column 6 to TIMESTAMP
原因:MySql数据库必须使用8.0.0+。
依赖:
mysql
mysql-connector-java
runtime
数据库配置:
# 数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/workflow_flowable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2b8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=1234