Flowable开发--Modeler集成(六)

一、Flowable-Modeler功能

https://gitee.com/lwj/flow-modeler-sduty/tree/master/src/main

提供可视化编辑器,编辑BPMN流程,编辑CASE模型,编辑Form表单,编辑App应用,编辑决策表
提供可视化参数配置:每个流程可以配置详细的参数设置,按照流程对应的规范来设计。
提供导入导出功能:方便将流程结果导入到其他应用程序
在我们实际项目中,我们的流程配置和表单都是在一个系统中操作的,不可能在flowable的war包上做流程配置。
所以集成modeler是flowable使用的开端。

二、源码下载与编译

  1. 源码下载
    下载地址:
    https://github.com/flowable/flowable-engine

    源码

  2. 导入idea编译


    导入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目录:


源汉化文件
目标汉化文件

四、启动测试

  1. 请求地址:http://localhost:8002/flowable-modeler/

    模型流程

  2. 创建流程


    创建流程

    创建流程

    设计流程

五、常见问题

  1. 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

你可能感兴趣的:(Flowable开发--Modeler集成(六))