关于Flowable的使用小结

目录

[1. Flowable-ui可视化界面部署和使用](#1. Flowable-ui可视化界面部署和使用)

[1.1 拉取镜像](#1.1 拉取镜像)

[1.2 添加lib依赖](#1.2 添加lib依赖)

[2. 在多数据源业务背景下集成Flowable](#2. 在多数据源业务背景下集成Flowable)

[2.1 添加maven依赖](#2.1 添加maven依赖)

[2.2 flowable初始化指定schema](#2.2 flowable初始化指定schema)

[3. Flowable常用Api](#3. Flowable常用Api)

[3.1 常用API](#3.1 常用API)

[3.2 Postman测试](#3.2 Postman测试)


最近在研究流程引擎Flowable,遇到了一些关于部署和多数据源的问题,所以做出如下的总结。

1. Flowable-ui可视化界面部署和使用

1.1 拉取镜像

通过Docker方式部署和启用Flowable-ui,可以用以下的方式拉取镜像。

bash 复制代码
docker run -d --name flowable -p 9080:8080 flowable/flowable-ui

启动后需要修改app/WEB-INF/classes/flowable-default.properties的数据源配置。

如果使用的DB是Docker部署的MySQL server可参考:

Docker部署Flowable-UI

为了部署方便也可以使用如下的方式:

bash 复制代码
docker run -d --name flowable-ui -p 9080:8080 -e SPRING_PROFILES_ACTIVE=mysql -e SPRING_DATASOURCE_URL="jdbc:mysql://IP:3306/flowable?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true" -e SPRING_DATASOURCE_USERNAME=root -e SPRING_DATASOURCE_PASSWORD=123456 -e SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver -e flowable.database-schema-update=ignore flowable/flowable-ui:6.8.0

但值得注意的是,其中的IP不能用localhost,甚至用Container name也不行,会导致Connection Refused的问题。

另外,如果需要部署Flowable-ui到Tomcat可以参考:Flowable UI Web应用搭建教程(图解)

1.2 添加lib依赖

如果DB用的是MySQL,那么需要在Container的app/WEB-INF/lib目录上传MySQL connector

上传完Restart就可以访问ui了。

bash 复制代码
http://127.0.0.1:9080/flowable-ui/#/


username: admin
password: test

如果登录显示invalid credentials,把镜像删掉重新pull就可以了。

2. 在多数据源业务背景下集成Flowable

2.1 添加maven依赖

java 复制代码
<dependency>
	<groupId>org.flowable</groupId>
	<artifactId>flowable-spring-boot-starter</artifactId>
	<version>6.8.0</version>
</dependency>

2.2 flowable初始化指定schema

通常来说我们会在jdbc url里面指定schema,如:

XML 复制代码
spring:
  datasource:
    dynamic:
      druid:
        initial-size: 5
        min-idle: 5
        maxActive: 20
        maxWait: 60000
        connectTimeout: 30000
        socketTimeout: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,slf4j
        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      datasource:
          # 主库数据源
          master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://127.0.0.1:3306/business?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048&nullCatalogMeansCurrent=true
            username: root
            password: 123456
          # 从库数据源
          flowable_db:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://127.0.0.1:3306/flowable?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=250&prepStmtCacheSqlLimit=2048&nullCatalogMeansCurrent=true
            username: root
            password: 123456

但Flowable启动的时候还是没办法用到指定的db flowable,具体源码分析和原因可以参考:Flowable多数据源配置以及指定schema

那如何指定呢?我们可以通过重写**org.flowable.app.engine.AppEngineConfiguration.buildAppEngine()**来实现。

java 复制代码
package com.bas.config;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import org.flowable.app.engine.AppEngine;
import org.flowable.app.spring.SpringAppEngineConfiguration;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.flowable.spring.SpringProcessEngineConfiguration;

@Configuration
public class FlowableConfig {

    @Autowired
    private DynamicRoutingDataSource dynamicDataSource;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    @Primary
    public SpringAppEngineConfiguration appEngineConfiguration() {
        SpringAppEngineConfiguration config = new SpringAppEngineConfiguration();

        DataSource flowableDs = dynamicDataSource.getDataSource("flowable_db");
        config.setDataSource(flowableDs);
        config.setTransactionManager(transactionManager);

        return config;
    }

    @Bean
    @Primary
    public AppEngine appEngine() {
        return appEngineConfiguration().buildAppEngine();
    }

    @Bean
    @Primary
    public ProcessEngine processEngine() {
        SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
        config.setTransactionManager(transactionManager);

        DataSource flowableDs = dynamicDataSource.getDataSource("flowable_db");
        config.setDataSource(flowableDs);

        return config.buildProcessEngine();
    }

    @Bean
    public RuntimeService runtimeService() {
        return processEngine().getRuntimeService();
    }

    @Bean
    public TaskService taskService() {
        return processEngine().getTaskService();
    }

    @Bean
    public RepositoryService repositoryService() {
        return processEngine().getRepositoryService();
    }

//    @EventListener(ApplicationReadyEvent.class)
//    public void deployProcessDefinitions() {
//        RepositoryService repositoryService = repositoryService();
//        repositoryService.createDeployment()
//                .addClasspathResource("definitionFiles/*.bpmn20.xml")
//                .deploy();
//    }

}

3. Flowable常用Api

3.1 常用API

假设现在有个flowable process id是testRequest,包含一个分支让用户去approve/reject/complete,我们可以创建一个Controller结合postman来测试。

java 复制代码
package com.bas.controller;

import com.bas.domain.AjaxResult;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @PostMapping("/request")
    public AjaxResult startTestRequest(@RequestBody Map<String, Object> variables) {
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testRequest", variables);
        return AjaxResult.success("test request started", processInstance.getId());
    }

    @GetMapping("/tasks")
    @Transactional
    public AjaxResult getPendingTasks() {
        List<Task> tasks = taskService.createTaskQuery()
                .taskCandidateGroup("managers")
                .list();
        
        List<Map<String, Object>> taskData = tasks.stream().map(task -> {
            Map<String, Object> taskInfo = new HashMap<>();
            taskInfo.put("id", task.getId());
            taskInfo.put("name", task.getName());
            taskInfo.put("assignee", task.getAssignee());
            taskInfo.put("createTime", task.getCreateTime());
            taskInfo.put("processInstanceId", task.getProcessInstanceId());
            return taskInfo;
        }).collect(Collectors.toList());
        
        return AjaxResult.success(taskData);
    }

    @GetMapping("/tasks/employee")
    @Transactional
    public AjaxResult getEmployeeTasks(@RequestParam String employee) {
        List<Task> tasks = taskService.createTaskQuery()
                .taskAssignee(employee)
                .list();
        
        List<Map<String, Object>> taskData = tasks.stream().map(task -> {
            Map<String, Object> taskInfo = new HashMap<>();
            taskInfo.put("id", task.getId());
            taskInfo.put("name", task.getName());
            taskInfo.put("assignee", task.getAssignee());
            taskInfo.put("createTime", task.getCreateTime());
            taskInfo.put("processInstanceId", task.getProcessInstanceId());
            return taskInfo;
        }).collect(Collectors.toList());
        
        return AjaxResult.success(taskData);
    }

    // approve
    @PostMapping("/approve/{taskId}")
    public AjaxResult approveTask(@PathVariable String taskId, @RequestParam boolean approved) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("approved", approved);
        taskService.complete(taskId, variables);
        return AjaxResult.success("Task completed");
    }

    @PostMapping("/complete/{taskId}")
    public AjaxResult completeTask(@PathVariable String taskId) {
        taskService.complete(taskId);
        return AjaxResult.success("Task completed");
    }

    // reject
    @GetMapping("/tasks/rejected")
    public AjaxResult getRejectedTasks(@RequestParam String employee) {
        List<Task> tasks = taskService.createTaskQuery()
                .taskAssignee(employee)
                .taskName("Task rejected")
                .list();
        return AjaxResult.success(tasks);
    }

//    @PostMapping("/acknowledge/{taskId}")
//    public AjaxResult acknowledgeRejection(@PathVariable String taskId) {
//        taskService.complete(taskId);
//        return AjaxResult.success("Rejection acknowledged");
//    }
}

3.2 Postman测试

以下是API对应的Postman request。

bash 复制代码
postman request POST 'localhost:9000/test/request' \
  --header 'Content-Type: application/json' \
  --body '{
    "employee": "Tester",
    "startDate": "2025-12-11", 
    "endDate": "2025-12-12",
    "reason": "Annual vacation",
    "days": 1
  }'
  
  
postman request 'localhost:9000/test/tasks' \
  --body ''
  
  
postman request 'localhost:9000/test/tasks/employee?employee=manager' \
  --body ''
  
  
postman request POST 'localhost:9000/test/approve/2514?approved=true' \
  --body ''
  
  
postman request POST 'localhost:9000/test/complete/2523' \
  --body ''

在Start process之后,具体的TaskID可以在这里查得到:http://127.0.0.1:9080/flowable-ui/admin/#/tasks

相关推荐
ForgeAI码匠11 小时前
ForgeAdmin|Spring Boot 3 后台框架的自动配置设计:少写配置,多做组合
java·spring boot·后端
tongluowan00711 小时前
Redisson的参数及工作原理
java·redis·lua·分布式锁
仙俊红12 小时前
Integer\int对比,equals()\hashcode面试
java·面试·职场和发展
WiChP12 小时前
【V0.1B10】从零开始的2D游戏引擎开发之路
java·数据库·游戏引擎
云烟成雨TD12 小时前
Spring AI Alibaba 1.x 系列【60】检查点机制原理与全流程剖析
java·人工智能·spring
ForgeAI码匠12 小时前
Maven 多模块项目如何避免越写越乱?Forge Admin 的模块边界实践
java·人工智能·开源·maven
z落落12 小时前
C# 数组 最终完整版全套笔记(一维+多维+交错+引用类型+对象数组)
java·笔记·c#
Access开发易登软件13 小时前
Access 和 SQLite,根本不在一个赛道上
java·jvm·数据库·sqlite·excel·vba·access开发
小马爱打代码13 小时前
Spring源码 第十篇:Spring 5 源码深度拆解 - Spring 类型转换与校验体系
java·spring
长谷深风11113 小时前
Java 面试高频:反射机制与异常体系全面解析
java·开发语言·面试·exception·java 反射·java 异常·class 对象