flowable流程設(shè)計(jì)器抽離灵汪,及源碼改造原理分析

業(yè)務(wù)場(chǎng)景:進(jìn)行流程開(kāi)發(fā)的時(shí)候檀训,經(jīng)常需要流程設(shè)計(jì)器進(jìn)行Bpmn.xml設(shè)計(jì),flowable官方也只是提供了war下載享言,并且需要基于本身的用戶(hù)權(quán)限峻凫,這對(duì)于已有系統(tǒng)的集成是不方便的,所以對(duì)于流程設(shè)計(jì)器的權(quán)限模塊剝離就很有必要览露,本文描述了如何對(duì)流程設(shè)計(jì)器的剝離以及核心模塊的功能分析荧琼。

環(huán)境
springboot:2.2.0.RELEASE
flowable:6.4.2

git地址:https://github.com/oldguys/flowable-modeler-demo.git
flowable 官方git地址:https://github.com/flowable/flowable-engine/releases
下一篇:《flowable流程設(shè)計(jì)器集成,整合mybatis-plus差牛,從modeler 中 部署流程定義 》

本地啟動(dòng)后測(cè)試路徑:http://localhost:8081/flowable-modeler/#/processes

01.png

流程設(shè)計(jì)器抽離demo

  1. pom文件
  2. 靜態(tài)資源及配置文件
  3. 重寫(xiě) flowable-modeler 默認(rèn)的類(lèi)加載
  4. 重寫(xiě) spring-security 相關(guān)模塊

Step 1:pom 文件

flowable-modeler 模塊:從官方源碼可以分析得出命锄,基本的modeler模塊的相關(guān)引用 此處 版本是 flowable 6.4.2
mysql數(shù)據(jù)源:默認(rèn)是h2數(shù)據(jù)庫(kù),此處引用的是mysql數(shù)據(jù)庫(kù)偏化。

        <!-- flowable-modeler 核心 -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-ui-modeler-conf</artifactId>
            <version>${flowable-version}</version>
        </dependency>
        <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>

        <!-- 替換數(shù)據(jù)源 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

Step 2: 靜態(tài)資源及配置文件

  1. 復(fù)制相關(guān)的靜態(tài)資源及配置文件
靜態(tài)原件及配置文件

在源碼中可以看到脐恩,這些文件為前端頁(yè)面的靜態(tài)頁(yè)面,需要完成轉(zhuǎn)移到剝離出來(lái)的新項(xiàng)目之中侦讨。

2.編寫(xiě)項(xiàng)目的自定義配置文件驶冒,及springboot本身項(xiàng)目的 application.yml 文件。

logging:
  level:
    org.flowable.ui.modeler.rest.app: debug
#    root: debug
server:
  port: 8081
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/flowable-modeler-demo?characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver

PS: 由于springboot 項(xiàng)目的配置文件是逐層覆蓋的韵卤,最外層項(xiàng)目的變量 可以直接覆蓋掉原始的配置變量骗污,所以可以直接在此處 配置數(shù)據(jù)源遍可以替換掉內(nèi)部的默認(rèn)數(shù)據(jù)源變量。

Step 3:重寫(xiě) flowable-modeler 默認(rèn)的類(lèi)加載

源碼 中的默認(rèn)類(lèi)加載

從源碼中看到入口為2個(gè)類(lèi)沈条,本處需要對(duì)org.flowable.ui.modeler.conf.ApplicationConfiguration 進(jìn)行相應(yīng)的改造需忿。
剔除不需要的相關(guān)依賴(lài),添加缺少的依賴(lài),改造結(jié)果如下:

  1. 抽出自定義需要的全局配置類(lèi)
package com.example.oldguy.modules.modeler.configurations;

import org.flowable.ui.modeler.conf.DatabaseConfiguration;
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.Import;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;


@Import(
        value = {
                DatabaseConfiguration.class,
        }
)
@Configuration
@EnableConfigurationProperties(FlowableModelerAppProperties.class)
@ComponentScan(basePackages = {
        "org.flowable.ui.modeler.repository",
        "org.flowable.ui.modeler.service",
        "org.flowable.ui.common.repository",
        "org.flowable.ui.common.tenant",

        "org.flowable.ui.modeler.rest.app",
        "org.flowable.ui.modeler.rest.api"
    }
)
public class MyAppConfiguration {

    @Bean
    public 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;
    }

}

PS: org.flowable.ui.modeler.conf.DatabaseConfiguration 為flowable源碼中的mybatis配置屋厘,需要引入汞扎。

  1. 重寫(xiě)類(lèi) org.flowable.ui.common.rest.idm.remote.RemoteAccountResource,這個(gè)類(lèi)是流程設(shè)計(jì)器獲取用戶(hù)相關(guān)信息的接口擅这,此處的處理方式是澈魄,在新建項(xiàng)目中編寫(xiě)相同的restful接口覆蓋,然后不加載原始的類(lèi)仲翎。
package com.example.oldguy.modules.modeler.controllers;

import org.flowable.ui.common.model.RemoteUser;
import org.flowable.ui.common.model.UserRepresentation;
import org.flowable.ui.common.security.FlowableAppUser;
import org.flowable.ui.common.security.SecurityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: RemoteAccountResource
 * @Author: ren
 * @Description:
 * @CreateTIme: 2020/1/25 0025 下午 10:37
 **/
@RestController
public class RemoteAccountResource {

    @GetMapping("app/rest/account")
    public UserRepresentation getAccount() {

        FlowableAppUser appUser = SecurityUtils.getCurrentFlowableAppUser();
        UserRepresentation userRepresentation = new UserRepresentation(appUser.getUserObject());
        if (appUser.getUserObject() instanceof RemoteUser) {
            RemoteUser temp = (RemoteUser) appUser.getUserObject();
            userRepresentation.setPrivileges(temp.getPrivileges());
        }
        return userRepresentation;
    }


}

  1. 重寫(xiě) org.flowable.ui.common.service.idm.RemoteIdmService 痹扇,此處在 flowable-ui-modeler-rest 模塊中多次依賴(lài)注入,本處項(xiàng)目暫時(shí)沒(méi)有使用到溯香,所以這里采用重寫(xiě)空實(shí)現(xiàn)鲫构。
package com.example.oldguy.modules.modeler.services;

import org.flowable.ui.common.model.RemoteGroup;
import org.flowable.ui.common.model.RemoteToken;
import org.flowable.ui.common.model.RemoteUser;
import org.flowable.ui.common.service.idm.RemoteIdmService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @ClassName: MyRemoteServiceImpl
 * @Author: ren
 * @Description:
 * @CreateTIme: 2020/1/25 0025 下午 11:27
 **/
@Service
public class MyRemoteServiceImpl implements RemoteIdmService {

    private Logger LOGGER = LoggerFactory.getLogger(MyRemoteServiceImpl.class);

    @Override
    public RemoteUser authenticateUser(String username, String password) {
        LOGGER.debug("MyRemoteServiceImpl:authenticateUser");
        return null;
    }

    @Override
    public RemoteToken getToken(String tokenValue) {
        LOGGER.debug("MyRemoteServiceImpl:getToken");
        return null;
    }

    @Override
    public RemoteUser getUser(String userId) {
        LOGGER.debug("MyRemoteServiceImpl:getUser");
        return null;
    }

    @Override
    public List<RemoteUser> findUsersByNameFilter(String filter) {
        LOGGER.debug("MyRemoteServiceImpl:findUsersByNameFilter");
        return null;
    }

    @Override
    public List<RemoteUser> findUsersByGroup(String groupId) {
        LOGGER.debug("MyRemoteServiceImpl:findUsersByGroup");
        return null;
    }

    @Override
    public RemoteGroup getGroup(String groupId) {
        LOGGER.debug("MyRemoteServiceImpl:getGroup");
        return null;
    }

    @Override
    public List<RemoteGroup> findGroupsByNameFilter(String filter) {
        LOGGER.debug("MyRemoteServiceImpl:findGroupsByNameFilter");
        return null;
    }
}

Step 4:重寫(xiě) spring-security 相關(guān)模塊

在源碼中 org.flowable.ui.modeler.conf.SecurityConfiguration 為流程設(shè)計(jì)器的權(quán)限核心控制,所以需要 進(jìn)行改造玫坛。本處直接基于spring-security 的運(yùn)行機(jī)制重寫(xiě)相關(guān)的實(shí)現(xiàn)類(lèi)结笨,不引用原始配置的相關(guān)信息

  1. 自定義認(rèn)證過(guò)濾器:com.example.oldguy.modules.modeler.security.MyFilter
package com.example.oldguy.modules.modeler.security;

import org.flowable.ui.common.security.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ClassName: MyFilter
 * @Author: huangrenhao
 * @Description:
 * @CreateTime: 2020/1/19 0019 下午 4:04
 * @Version:
 **/
@Component
@WebFilter(urlPatterns = {"/app/**", "/api/**"})
public class MyFilter extends OncePerRequestFilter {

    private Logger LOGGER = LoggerFactory.getLogger(MyFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (skipAuthenticationCheck(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        LOGGER.debug("MyFilter:doFilterInternal:" + request.getRequestURL());

        if (StringUtils.isEmpty(SecurityUtils.getCurrentUserId())) {

            LOGGER.debug("MyFilter:doFilterInternal:校驗(yàn)......");
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("admin", "");
            SecurityContextHolder.getContext().setAuthentication(token);

        } else {
            LOGGER.debug("MyFilter:doFilterInternal:校驗(yàn)通過(guò).......");
        }

        filterChain.doFilter(request, response);
    }

    protected boolean skipAuthenticationCheck(HttpServletRequest request) {
        return request.getRequestURI().endsWith(".css") ||
                request.getRequestURI().endsWith(".js") ||
                request.getRequestURI().endsWith(".html") ||
                request.getRequestURI().endsWith(".map") ||
                request.getRequestURI().endsWith(".woff") ||
                request.getRequestURI().endsWith(".png") ||
                request.getRequestURI().endsWith(".jpg") ||
                request.getRequestURI().endsWith(".jpeg") ||
                request.getRequestURI().endsWith(".tif") ||
                request.getRequestURI().endsWith(".tiff");
    }

}

PS: 經(jīng)過(guò)試驗(yàn),過(guò)濾器必須繼承于 org.springframework.web.filter.OncePerRequestFilter 而不能直接實(shí)現(xiàn) javax.servlet.Filter 湿镀,不然就算注入到 spring-security容器中炕吸,也不能觸發(fā)本身的權(quán)限校驗(yàn),具體原理還有待研究勉痴。此處參考源碼中的 org.flowable.ui.common.filter.FlowableCookieFilter進(jìn)行改造

  1. 編寫(xiě)核心的校驗(yàn)類(lèi) com.example.oldguy.modules.modeler.security.MyUserDetailsService
package com.example.oldguy.modules.modeler.security;

import org.flowable.ui.common.model.RemoteUser;
import org.flowable.ui.common.security.FlowableAppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

import static com.example.oldguy.modules.modeler.constants.FlowableConstants.FLOW_ABLE_MODELER_ROLES;

/**
 * @ClassName: MyUserDetailsService
 * @Author: huangrenhao
 * @Description:
 * @CreateTime: 2020/1/17 0017 下午 4:37
 * @Version:
 **/
@Service
public class MyUserDetailsService implements UserDetailsService {

    private Logger LOGGER = LoggerFactory.getLogger(MyUserDetailsService.class);

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        LOGGER.debug("MyUserDetailsService:loadUserByUsername:認(rèn)證權(quán)限.....");
        ArrayList<GrantedAuthority> authorities = new ArrayList<>();

        // 配置 flowable-modeler 權(quán)限
        FLOW_ABLE_MODELER_ROLES.parallelStream().forEach(obj -> {
            authorities.add(new SimpleGrantedAuthority(obj));
        });

        RemoteUser sourceUser = new RemoteUser();
        sourceUser.setFirstName("admin");
        sourceUser.setDisplayName("測(cè)試中文");
        sourceUser.setPassword("123456");
        sourceUser.setPrivileges(new ArrayList<>(FLOW_ABLE_MODELER_ROLES));
        sourceUser.setId("123456");
        FlowableAppUser user = new FlowableAppUser(sourceUser, "admin", authorities);
        return user;
    }
}

  1. 編寫(xiě)密碼編譯器 com.example.oldguy.modules.modeler.security.MyPasswordEncoder
package com.example.oldguy.modules.modeler.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @ClassName: MyPasswordEncoder
 * @Author: ren
 * @Description:
 * @CreateTIme: 2020/1/25 0025 下午 7:11
 **/
@Component
public class MyPasswordEncoder implements PasswordEncoder {

    private Logger LOGGER = LoggerFactory.getLogger(MyPasswordEncoder.class);

    @Override
    public String encode(CharSequence charSequence) {
        LOGGER.debug("MyPasswordEncoder:encode:" + charSequence);
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence frontPsw, String sourcePsw) {
        LOGGER.debug("MyPasswordEncoder:matches:" + frontPsw + "\t sourcePsw:" + sourcePsw);
        return true;
    }
}
  1. 編寫(xiě)spring-security 核心容器
package com.example.oldguy.modules.modeler.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.web.filter.CorsFilter;

/**
 * @ClassName: WebSecurityConfig
 * @Author: huangrenhao
 * @Description:
 * @CreateTime: 2020/1/19 0019 下午 3:16
 * @Version:
 **/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyFilter myFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .addFilterAfter(myFilter, SecurityContextPersistenceFilter.class)
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and().csrf().disable();
    }

}

PS: csrf 要關(guān)掉赫模,不然前端調(diào)用會(huì)被攔截。出現(xiàn) Forbidden 一閃而過(guò)的情況蒸矛。

以上就完成流程設(shè)計(jì)器抽離瀑罗。


下面是對(duì)于其中的部分模塊及原理的解析:

  1. org.flowable.ui.common.security.SecurityUtils 用戶(hù)信息
  2. 模型設(shè)計(jì)器配置相應(yīng)模塊
  3. spring-security 過(guò)濾鏈
1. org.flowable.ui.common.security.SecurityUtils 用戶(hù)信息
// org.flowable.ui.common.security.SecurityUtils
    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;
    }
// com.example.oldguy.modules.modeler.security.MyUserDetailsService
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        LOGGER.debug("MyUserDetailsService:loadUserByUsername:認(rèn)證權(quán)限.....");
        ArrayList<GrantedAuthority> authorities = new ArrayList<>();

        // 配置 flowable-modeler 權(quán)限
        FLOW_ABLE_MODELER_ROLES.parallelStream().forEach(obj -> {
            authorities.add(new SimpleGrantedAuthority(obj));
        });

        RemoteUser sourceUser = new RemoteUser();
        sourceUser.setFirstName("admin");
        sourceUser.setDisplayName("測(cè)試中文");
        sourceUser.setPassword("123456");
        sourceUser.setPrivileges(new ArrayList<>(FLOW_ABLE_MODELER_ROLES));
        sourceUser.setId("123456");
        FlowableAppUser user = new FlowableAppUser(sourceUser, "admin", authorities);
        return user;
    }
spring-security 官網(wǎng)的 SecurityContextHolder 描述

PS: 從官網(wǎng)中可以看到 用戶(hù)是基于 ThreadLocal 的,所以是線(xiàn)程安全的雏掠,并且在 SecurityUtils 調(diào)用的時(shí)候 FlowableAppUser 才會(huì)返回斩祭,所以在重寫(xiě) org.springframework.security.core.userdetails.UserDetailsService 實(shí)現(xiàn)類(lèi)的時(shí)候,需要使用 FlowableAppUser 作為用戶(hù)類(lèi)乡话。

另外摧玫,restful接口:http://localhost:8888/flowable-modeler/app/rest/account
默認(rèn)返回值:

{
    "id": "admin",
    "firstName": "Test",
    "lastName": "Administrator",
    "email": "admin@flowable.org",
    "fullName": "Test Administrator",
    "tenantId": null,
    "groups": [],
    "privileges": ["access-idm", "access-rest-api", "access-task", "access-modeler", "access-admin"]
}

其中:["access-idm", "access-rest-api", "access-task", "access-modeler", "access-admin"]
分別對(duì)應(yīng)流程設(shè)計(jì)器導(dǎo)航欄上面的各個(gè)功能,可以根據(jù)需要進(jìn)行刪減改造


image.png
2. 模型設(shè)計(jì)器配置相應(yīng)模塊

模型設(shè)計(jì)器中功能特別多蚊伞,并不是所有功能都需要用到的席赂,可以通過(guò)修改配置文件進(jìn)行修改
資源位置:

stencilset_bpmn.json(流程設(shè)計(jì)器界面配置文件):D:\workspace\demo\flowable\flowable-engine-flowable-6.4.2-old\modules\flowable-ui-modeler\flowable-ui-modeler-logic\src\main\resources\stencilset_bpmn.json

StencilSetResource(流程設(shè)計(jì)器配置后端接口): org.flowable.ui.modeler.rest.app.StencilSetResource

zh-CN.json(可參考的漢化文件):static/i18n/zh-CN.json

配置文件的位置
配置接口位置
修改完配置結(jié)果
3. spring-security 過(guò)濾鏈

spring-security的過(guò)濾鏈 基于 org.springframework.security.config.annotation.web.builders.FilterComparator
debug的時(shí)候可以拿到數(shù)據(jù)(如下),所以配置過(guò)濾器的時(shí)候时迫,必須優(yōu)先于指定的節(jié)點(diǎn)颅停,如LogoutFilter,如:CorsFilter=600掠拳,如果后于此過(guò)濾器癞揉,會(huì)出現(xiàn)沒(méi)有權(quán)限,然后界面一閃而過(guò)。很難調(diào)試喊熟,所以需要通過(guò)過(guò)濾鏈順序?qū)τ谥付ǖ臋?quán)限攔截進(jìn)行處理柏肪。

{
org.springframework.security.openid.OpenIDAuthenticationFilter=1600, 
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter=1300, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter=1800, 
org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter=900, 
org.springframework.security.web.context.SecurityContextPersistenceFilter=400, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor=3100, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter=1700, 
org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter=1000, 
org.springframework.security.web.session.SessionManagementFilter=2900, 
org.springframework.security.web.authentication.logout.LogoutFilter=800, 
org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter=1100, 
org.springframework.security.web.jaasapi.JaasApiIntegrationFilter=2500, 
org.springframework.security.web.authentication.switchuser.SwitchUserFilter=3200, 
org.springframework.security.web.access.channel.ChannelProcessingFilter=100, 
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter=2600, 
org.springframework.security.web.session.ConcurrentSessionFilter=1900, 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter=2200, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter=2700, 
org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter=2100, 
org.springframework.security.web.csrf.CsrfFilter=700, 
org.springframework.security.cas.web.CasAuthenticationFilter=1200, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter=2400, 
org.springframework.security.web.access.ExceptionTranslationFilter=3000, 
org.springframework.security.web.authentication.www.DigestAuthenticationFilter=2000,
 org.springframework.web.filter.CorsFilter=600, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter=2300, 
org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter=2800, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter=1400,
org.springframework.security.web.header.HeaderWriterFilter=500, 
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter=300
}

獲取已保存的 bpmn.xml

源碼:org.flowable.ui.modeler.rest.app.ModelBpmnResource
調(diào)用接口:127.0.0.1:8081/flowable-modeler/app/rest/models/9f545d3b-40ac-11ea-9aaf-283a4d3b99a3/bpmn20

效果圖
<?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="test" name="test" isExecutable="true">
        <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
        <userTask id="sid-AF5CC33E-0CE1-49EE-AEF6-6473F785A10E" name="測(cè)試" flowable:formFieldValidation="true"></userTask>
        <sequenceFlow id="sid-8E539945-3C77-4630-90D1-984D585F8AE9" sourceRef="startEvent1" targetRef="sid-AF5CC33E-0CE1-49EE-AEF6-6473F785A10E"></sequenceFlow>
        <endEvent id="sid-C633FA15-0F2F-4739-B576-EE0C2072B40F"></endEvent>
        <sequenceFlow id="sid-64160218-43FF-4F5A-B51A-8C11213ACC8D" sourceRef="sid-AF5CC33E-0CE1-49EE-AEF6-6473F785A10E" targetRef="sid-C633FA15-0F2F-4739-B576-EE0C2072B40F"></sequenceFlow>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_test">
        <bpmndi:BPMNPlane bpmnElement="test" id="BPMNPlane_test">
            <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-AF5CC33E-0CE1-49EE-AEF6-6473F785A10E" id="BPMNShape_sid-AF5CC33E-0CE1-49EE-AEF6-6473F785A10E">
                <omgdc:Bounds height="80.0" width="100.0" x="214.39999999999998" y="144.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-C633FA15-0F2F-4739-B576-EE0C2072B40F" id="BPMNShape_sid-C633FA15-0F2F-4739-B576-EE0C2072B40F">
                <omgdc:Bounds height="28.0" width="28.0" x="407.4" y="170.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="sid-8E539945-3C77-4630-90D1-984D585F8AE9" id="BPMNEdge_sid-8E539945-3C77-4630-90D1-984D585F8AE9">
                <omgdi:waypoint x="129.93799288040108" y="178.5999272073778"></omgdi:waypoint>
                <omgdi:waypoint x="214.39999999999998" y="181.99196787148597"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-64160218-43FF-4F5A-B51A-8C11213ACC8D" id="BPMNEdge_sid-64160218-43FF-4F5A-B51A-8C11213ACC8D">
                <omgdi:waypoint x="314.35" y="184.0"></omgdi:waypoint>
                <omgdi:waypoint x="407.4" y="184.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绑改,一起剝皮案震驚了整個(gè)濱河市敛滋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌龄寞,老刑警劉巖壁拉,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬俄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡弃理,警方通過(guò)查閱死者的電腦和手機(jī)溃论,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痘昌,“玉大人钥勋,你說(shuō)我怎么就攤上這事×咎Γ” “怎么了算灸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)姑子。 經(jīng)常有香客問(wèn)我乎婿,道長(zhǎng),這世上最難降的妖魔是什么街佑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮捍靠,結(jié)果婚禮上沐旨,老公的妹妹穿的比我還像新娘。我一直安慰自己榨婆,他們只是感情好磁携,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著良风,像睡著了一般谊迄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烟央,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天统诺,我揣著相機(jī)與錄音,去河邊找鬼疑俭。 笑死粮呢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啄寡,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼豪硅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了挺物?” 一聲冷哼從身側(cè)響起懒浮,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎识藤,沒(méi)想到半個(gè)月后嵌溢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹋岩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年赖草,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剪个。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秧骑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扣囊,到底是詐尸還是另有隱情乎折,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布侵歇,位于F島的核電站骂澄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惕虑。R本人自食惡果不足惜坟冲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望溃蔫。 院中可真熱鬧健提,春花似錦、人聲如沸伟叛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)统刮。三九已至紊遵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侥蒙,已是汗流浹背暗膜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辉哥,地道東北人桦山。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓攒射,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恒水。 傳聞我的和親對(duì)象是個(gè)殘疾皇子会放,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容