Springboot整合Flowable并进行一个通用审批流程应用实践
一、Flowable介绍
##官方文档
https://www.flowable.org/docs/userguide/index.html
Flowable是一个使用Java编写的轻量级业务流程引擎
Flowable是Activiti的fork
二、springboot整合Flowable6.7.2
基本的整合是参考这篇文章的,CSDN博主「水中加点糖」这位大神的采用springboot+flowable快速实现工作流,阅读量达20万以上,太厉害了,按照这篇文章做下来,结合我的实际情况,很顺利完成基本的整合
1.springboot和flowable的版本
springboot的2.6.2版本和的flowable的6.7.2比较搭配,因此使用这2个版本配对
springBoot版本:2.6.2
flowable版本:6.7.2
2.pom.xml引入
引入flowable-spring-boot-starter和mybatis相关
<properties>
<flowable.version>6.7.2</flowable.version>
</properties>
<!--flowable工作流依赖 start -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>${flowable.version}</version>
</dependency>
<!--flowable工作流依赖 end -->
<!-- mybatis start -->
<!--pagehelper分页 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.starter.version}</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql jdbc -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>5.1.45</version> -->
</dependency>
<!-- mybatis end -->
3.application.yml中配置flowable的参数
关闭定时任务JOB及启动检查数据库创建参数database-schema-update
flowable:
#关闭定时任务JOB
async-executor-activate: false
database-schema-update: true
4.建好数据库flowabletest,然后启动
启动时有个注意的:
我的springboot和flowable版本的搭配如果配的是mysql8.0的驱动,会报表不存在的错误,因此先改成5.1.45的驱动,会成功创建表
CREATE DATABASE `flowabletest` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */
我这边测试时用mysql8.0驱动时,会报表不存在
,然后将以下注解开放,即mysql驱动改成5.1.45,application.yml中也相应修改驱动名为com.mysql.jdbc.Driver,然后重启,可以新建表成功。如果你没有这个问题,可以不用管这一个
,建表成功后,可以将flowable.database-schema-update参数改成false
,这个生产环境下建议还是改成false
,另外建表成功后,可以把mysql的驱动恢复成8.0的(用8.0驱动建表不成功原因不明)。
<!-- mysql jdbc -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>5.1.45</version> -->
</dependency>
5.定义流程文件
这里我也就直接复制上面引用文章中的流程文件,有不少的人是直接复制这个流程文件的,但自己建的包名又不一样,因此会报包不存在的问题,因此,我们拿到这个流程文件内容后,将里面的类全名路径,可以改成你自己的包名里面去,然后将其命令为ExpenseProcess.bpmn20.xml并将其放于项目中的resource目录下的processes(如此目录不存在自行创建)目录下
<?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: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="Expense" name="ExpenseProcess" isExecutable="true">
<documentation>报销流程</documentation>
<startEvent id="start" name="开始"></startEvent>
<userTask id="fillTask" name="出差报销" flowable:assignee="${taskUser}">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler">
<![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="judgeTask"></exclusiveGateway>
<userTask id="directorTak" name="经理审批">
<extensionElements>
<flowable:taskListener event="create"
class="cn.gzsendi.flowable.listeners.ManagerTaskHandler"></flowable:taskListener>
</extensionElements>
</userTask>
<userTask id="bossTask" name="老板审批">
<extensionElements>
<flowable:taskListener event="create"
class="cn.gzsendi.flowable.listeners.BossTaskHandler"></flowable:taskListener>
</extensionElements>
</userTask>
<endEvent id="end" name="结束"></endEvent>
<sequenceFlow id="directorNotPassFlow" name="驳回" sourceRef="directorTak" targetRef="fillTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="fillTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1" sourceRef="start" targetRef="fillTask"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="fillTask" targetRef="judgeTask"></sequenceFlow>
<sequenceFlow id="judgeMore" name="大于500元" sourceRef="judgeTask" targetRef="bossTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${money > 500}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="directorPassFlow" name="通过" sourceRef="directorTak" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="judgeLess" name="小于500元" sourceRef="judgeTask" targetRef="directorTak">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${money <= 500}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_Expense">
<bpmndi:BPMNPlane bpmnElement="Expense" id="BPMNPlane_Expense">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="285.0" y="135.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fillTask" id="BPMNShape_fillTask">
<omgdc:Bounds height="80.0" width="100.0" x="405.0" y="110.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
<omgdc:Bounds height="40.0" width="40.0" x="585.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="directorTak" id="BPMNShape_directorTak">
<omgdc:Bounds height="80.0" width="100.0" x="735.0" y="110.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
<omgdc:Bounds height="80.0" width="100.0" x="555.0" y="255.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="771.0" y="281.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="315.0" y="150.0"></omgdi:waypoint>
<omgdi:waypoint x="405.0" y="150.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="505.0" y="150.16611295681062"></omgdi:waypoint>
<omgdi:waypoint x="585.4333333333333" y="150.43333333333334"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
<omgdi:waypoint x="624.5530726256983" y="150.44692737430168"></omgdi:waypoint>
<omgdi:waypoint x="735.0" y="150.1392757660167"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="directorNotPassFlow" id="BPMNEdge_directorNotPassFlow">
<omgdi:waypoint x="785.0" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="785.0" y="37.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="37.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="110.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
<omgdi:waypoint x="655.0" y="295.0"></omgdi:waypoint>
<omgdi:waypoint x="771.0" y="295.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
<omgdi:waypoint x="605.4340277777778" y="169.56597222222223"></omgdi:waypoint>
<omgdi:waypoint x="605.1384083044983" y="255.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="directorPassFlow" id="BPMNEdge_directorPassFlow">
<omgdi:waypoint x="785.0" y="190.0"></omgdi:waypoint>
<omgdi:waypoint x="785.0" y="281.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
<omgdi:waypoint x="555.0" y="295.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="295.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="190.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
6.代理类准备
ManagerTaskHandler
package cn.gzsendi.flowable.listeners;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
public class ManagerTaskHandler implements TaskListener {
private static final long serialVersionUID = -326245510542194886L;
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("经理");
}
}
BossTaskHandler
package cn.gzsendi.flowable.listeners;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
public class BossTaskHandler implements TaskListener {
private static final long serialVersionUID = 1L;
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("老板");
}
}
6.启动springboot工程让springboot帮我们部署好流程
到这直接启动springboot工程,如果不报错就会将processes下的我们刚准备的流程部署至数据库中,也可以通过检查数据库的表中是否存在部署记录,比如
7.接下来写controller类进行测试
这里我还是直接引用文章开头的引用文章里面的内容,复制出来定义成我的。
写一个TestController类
package cn.gzsendi.flowable.controller;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private ProcessEngine processEngine;
/**
* 通过接收用户的一个请求传入用户的ID和金额以及描述信息来开启一个报销流程,并返回给用户这个流程的Id
* 添加报销
* @param userId 用户Id
* @param money 销金额
* @param descption 描述
*/
@GetMapping("/add")
public String addExpense(String userId, Integer money, String descption) {
//启动流程
HashMap<String, Object> map = new HashMap<>();
map.put("taskUser", userId);
map.put("money", money);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Expense", map);
return "提交成功.流程Id为:" + processInstance.getId();
}
/**
* 查询流程列表,待办列表,通过代码获取出用户需要处理的流程
* 获取审批管理列表
*/
@GetMapping("/list")
public Object list(String userId) {
List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
for (Task task : tasks) {
System.out.println(task.toString());
}
return "task size: " +tasks.size() + " , 第一个:" + tasks.get(0).toString() ;
}
/**
* 批准,通过前端传入的任务ID来对此流程进行同意处理
* @param taskId 任务ID
*/
@GetMapping("/apply")
public String apply(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("流程不存在");
}
//通过审核
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "通过");
taskService.complete(taskId, map);
return "processed ok!";
}
/**
* 拒绝
*/
@GetMapping("/reject")
public String reject(String taskId) {
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "驳回");
taskService.complete(taskId, map);
return "reject";
}
/**
* 生成当前流程图表
* 生成流程图
* @param processId 任务ID
*/
@GetMapping("/processDiagram")
public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
//流程走完的不显示图
if (pi == null) {
return;
}
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
//使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
String InstanceId = task.getProcessInstanceId();
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(InstanceId)
.list();
//得到正在执行的Activity的Id
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
//图片输出要加上这个不会显示二进制数据,有些浏览器正常,有些浏览器是直接显示二进制数据,因此修改
httpServletResponse.setContentType("image/png".concat(";charset=UTF-8"));
//获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0,true);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = httpServletResponse.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
8.接下来可以进行演示了
1.创建一个流程:
访问:http://localhost:8080/test/add?userId=testuser&money=1234
返回:提交成功.流程Id为:cd8b3025-c6c9-11ec-b88b-02004c4f4f50
2.查询待办列表:
访问:http://localhost:8080/test/list?userId=testuser
输出:task size: 1 , 第一个:Task[id=cd93949c-c6c9-11ec-b88b-02004c4f4f50, name=出差报销]
3.同意:
访问:http://localhost:8080/test/apply?taskId=cd93949c-c6c9-11ec-b88b-02004c4f4f50
返回:processed ok!
4.生成流程图:
访问:http://localhost:8080/test/processDiagram?processId=cd8b3025-c6c9-11ec-b88b-02004c4f4f50
发现流程乱码
进一步处理,在工程中加上类FlowableConfig
,内容如下:
package cn.gzsendi.config;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;
/**
* @author haiyangp
* date: 2018/4/7
* desc: flowable配置----为放置生成的流程图中中文乱码
*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
engineConfiguration.setActivityFontName("宋体");
engineConfiguration.setLabelFontName("宋体");
engineConfiguration.setAnnotationFontName("宋体");
}
}
重新启动工程测试,至此,图片也正常了,如图
到这里基本的flowable初探过程结束
。
三、Spring Boot 整合 Flowable-ui-modeler 6.7.2
初探完成,接着就需要整合Flowable-ui-modeler这个在线设计流程器了,这个也同样的借鉴了另一个大神的文章,CSDN博主「wangdaoyin2010」的Spring Boot 整合 Flowable-ui-modeler 6.7.2,顺利的完成了整合modeler过程,其间也有一些自己的调整。
1.下载前端文件
- 下载flowable-engine-main源码文件
https://github.com/flowable/flowable-engine/releases/tag/flowable-6.7.2
注:下源码不是编绎后的
wget https://github.com/flowable/flowable-engine/archive/refs/tags/flowable-6.7.2.tar.gz - 找到前端文件
解压下载后的文件,找到“flowable-engine-main\modules\flowable-ui\flowable-ui-modeler-frontend\src\main\resources\static”文件夹。将整个”static”文件夹拷贝到自己项目下“resources”目录中 - 启动测试下
http://localhost:8080/modeler/index.html
2.添加Flowable-ui-modeler依赖
在前面工程的依赖基础之上继续添加依赖
<!-- 添加flowable-ui-modeler核心依赖项 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-rest</artifactId>
<version>${flowable.version}</version>
</dependency>
<!--添加flowable-ui-modeler配置依赖项 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-conf</artifactId>
<version>${flowable.version}</version>
</dependency>
flowable-ui-modeler-rest: 包含flowableUI对模型的增删查改等所有模型维护接口
flowable-ui-modeler-conf: 包含了一些ui-modeler的自动化配置项
3.先启动测试一波
报错,Caused by: java.lang.IllegalArgumentException: flowable.common.app.idm-url
must be set
4.配置idm
继续摘录大神文章中的内容,没有进行Flowable默认使用idm授权,需要对idm授权信息进行配置,添加对应idm配置,继续在application.yml
中加入配置如下,然后重启工程。
flowable:
#关闭定时任务JOB
async-executor-activate: false
database-schema-update: true
common:
app:
# 目前先设置一个正确但是不一定可用的url地址
idm-url: http://localhost:8080/flowable-idm1
idm-admin:
# 需要设置一个密码,目前先设置,后期不使用
password: test
# 默认user为admin,这个地方可设置可不设置
user: admin1
5.绕过Flowable授权
再次启动成功,然后访问http://localhost:8080/modeler
会跳转到http://localhost:8080/flowable-idm1/idm/#/login?redirectOnAuthSuccess=true&redirectUrl=http://localhost:8080/modeler/
该地址中http://localhost:8080/flowable-idm1就是前面配置idm认证授权地址。说明要访问页面需要进行授权,这里我们不进行授权,直接配置绕过Flowable中的授权。
CSDN博主「wangdaoyin2010」大神的Spring Boot 整合 Flowable-ui-modeler 6.7.2里面写得比较细,我这里就直接使用过程不纠原理了,继续。
- 创建一个授权配置类SecurityConfiguration,可以放在任何包下。
package cn.gzsendi.config;
import org.flowable.ui.common.security.SecurityConstants;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfiguration {
@Configuration(proxyBeanMethods = false)
//Order配置说明
// 这个地方相同会报错
//这个地方如果大于则该配置在FlowableUiSecurityAutoConfiguratio中对应项后加载,不能起到绕过授权作用
//所以这个地方-1让该配置项在FlowableUiSecurityAutoConfiguratio中对应配置项前加载,以跳过授权
@Order(SecurityConstants.FORM_LOGIN_SECURITY_ORDER - 1)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//必须要将csrf设置为disable,不然后面发送POST请求时会报403错误
.csrf().disable()
//为了简单起见,简单粗暴方式直接放行modeler下面所有请求
.authorizeRequests().antMatchers("/modeler/**").permitAll();
}
}
}
6.配置账号信息
-
重新启动后通过
http://localhost:8080/modeler/
看效果如下,基本的东西出来了,就是差菜单。
按F12
看接口情况,也发现有一个获取账号的接口报500
错误
该请求为获取当前用户信息接口 -
重写RemoteAccountResource这个类的代码,细节还是可以看大神的文章,这里继续操作过程。
创建一个类覆盖原来的RemoteAccountResource类(该类所在的包名、类名必须与原类相同弄才能覆盖),在该类中重新实现getAccount方法,直接返回一个用户信息既可。
注意:因为返回的用户信息,前端需要判断角色权限,所以最好该方法中会添加一些角色权限信息返回。直接使用如下代码既可
package cn.gzsendi.flowable.controller;
import java.util.ArrayList;
import java.util.List;
import org.flowable.ui.common.model.UserRepresentation;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.common.service.exception.NotFoundException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping({
"/app",
"/"
})
public class RemoteAccountResource {
/**
* GET /rest/account -> get the current user.
*/
@RequestMapping(value = "/rest/account", method = RequestMethod.GET, produces = "application/json")
public UserRepresentation getAccount() {
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setFirstName("admin");
userRepresentation.setLastName("admin");
userRepresentation.setFullName("admin");
userRepresentation.setId("admin");
List<String> 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();
}
}
}
- 重启测试还是会报错的,路径不对,找到
url-config.js配置文件中的请求地址
进行修改
共两个url-config.js,找到路径为src\main\resources\static\modeler\scripts\configuration\url-config.js
的这个进行改即可。
修改前:
getAccountUrl: function () {
return FLOWABLE.CONFIG.contextModelerRestRoot + '/rest/account';
},
修改后
getAccountUrl: function () {
return FLOWABLE.CONFIG.contextRoot + '/app/rest/account';
},
刷新看效果如图:
7. Flowable Modeler匿名账号的处理
现在,基本上能进行在线流程设计了,我简单设计一下如下:
保存后显示匿名用户创建的:
网上找了一波,要改
org.flowable.ui.common.security.SecurityUtils
的类,进行重写覆盖到工程里面(包名路径等不能修改
)
package org.flowable.ui.common.security;
import java.util.ArrayList;
import java.util.List;
import org.flowable.common.engine.api.FlowableIllegalStateException;
import org.flowable.idm.api.User;
import org.flowable.ui.common.model.RemoteUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* 重构流程编辑器获取用户信息
*/
public class SecurityUtils {
private static User assumeUser;
private static SecurityScopeProvider securityScopeProvider = new FlowableSecurityScopeProvider();
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;
}
RemoteUser user = new RemoteUser();
user.setId("admin");
user.setDisplayName("admin");
user.setFirstName("admin");
user.setLastName("admin");
user.setEmail("admin@flowable.com");
user.setPassword("123456");
List<String> 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 void setSecurityScopeProvider(SecurityScopeProvider securityScopeProvider) {
SecurityUtils.securityScopeProvider = securityScopeProvider;
}
public static SecurityScope getCurrentSecurityScope() {
SecurityContext securityContext = SecurityContextHolder.getContext();
if (securityContext != null && securityContext.getAuthentication() != null) {
return getSecurityScope(securityContext.getAuthentication());
}
return null;
}
public static SecurityScope getSecurityScope(Authentication authentication) {
return securityScopeProvider.getSecurityScope(authentication);
}
public static SecurityScope getAuthenticatedSecurityScope() {
SecurityScope currentSecurityScope = getCurrentSecurityScope();
if (currentSecurityScope != null) {
return currentSecurityScope;
}
throw new FlowableIllegalStateException("User is not authenticated");
}
public static void assumeUser(User user) {
assumeUser = user;
}
public static void clearAssumeUser() {
assumeUser = null;
}
}
如图:
8.重启测试
重启测试后,重新加一个流程,匿名账号的问题也解决了,一切正常。
9.在线流程进行发布
设计了在线流程后,通过flowable moderler怎么在线发布流程,新的flowable-ui,可通过应用程序进行发布
。
- 新加一个应用程序
然后进行创建好的应用程序进行编辑
应用程序进行编辑过程,可以选中想发布的流程,然后在点发布即可。
发布后数据库就会有记录
至此,整合Modeler完成。
四、通用审批流程的应用实践
前面的flowable整合完后,已经基本上能按业务需求进行使用了,但每个流程要设计一次,还是觉得有点麻烦,对于我们一些简单的业务,就只设计配置一个通用的流程,系统中的流程都转化成这个通用的流程来进行审批流转
1.通用审批流程设计
通过flowable modeler进行设计一个通用流程如下,系统中的流程会转化成这个流程来进行审批流转
附上对应的通用审批流程.bpmn20.xml(只是参考用,因为可以通过在线Modeler进行修改):
<?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" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="CommonExamineAndApproveFlow" name="通用审批流程" isExecutable="true">
<startEvent id="startCEAAF" name="开始" flowable:formFieldValidation="false">
<extensionElements>
<flowable:executionListener event="start" class="cn.gzsendi.flowable.listener.CommonFlowExecListener"></flowable:executionListener>
</extensionElements>
</startEvent>
<userTask id="examAndApprove" name="审批" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="create" class="cn.gzsendi.flowable.listener.CommonFlowTaskListener"></flowable:taskListener>
<flowable:taskListener event="complete" class="cn.gzsendi.flowable.listener.CommonFlowTaskListener"></flowable:taskListener>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<userTask id="modification" name="修改" flowable:assignee="${applicant}" flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="create" class="cn.gzsendi.flowable.listener.CommonFlowTaskListener"></flowable:taskListener>
<flowable:taskListener event="complete" class="cn.gzsendi.flowable.listener.CommonFlowTaskListener"></flowable:taskListener>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="sid-801B443D-1EE6-4B3D-A574-6939856E31BD"></exclusiveGateway>
<exclusiveGateway id="sid-E4B59163-B3E3-47D2-94C2-D079D90F2673"></exclusiveGateway>
<exclusiveGateway id="checkApproveResult"></exclusiveGateway>
<endEvent id="endCEAAF" name="结束">
<extensionElements>
<flowable:executionListener event="end" class="cn.gzsendi.flowable.listener.CommonFlowExecListener"></flowable:executionListener>
</extensionElements>
</endEvent>
<endEvent id="cancelCEAAF" name="取消">
<extensionElements>
<flowable:executionListener event="end" class="cn.gzsendi.flowable.listener.CommonFlowExecListener"></flowable:executionListener>
</extensionElements>
</endEvent>
<sequenceFlow id="sid-C21B362D-DC54-4507-B9C8-511450686938" name="提交申请" sourceRef="startCEAAF" targetRef="examAndApprove"></sequenceFlow>
<sequenceFlow id="sid-94CE2A65-C720-4B6B-BCE4-363269AAB6A0" name="下一个人继续批" sourceRef="checkApproveResult" targetRef="examAndApprove">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result>0}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-54D272DB-EBF8-4944-914B-69D77C3E89D1" name="通过" sourceRef="checkApproveResult" targetRef="endCEAAF">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==0}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-39DAAB85-AF7E-4D7A-A4F3-66226719D9AC" name="不通过" sourceRef="checkApproveResult" targetRef="sid-E4B59163-B3E3-47D2-94C2-D079D90F2673">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result<0}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-931CBA3F-5300-4C16-98BB-73AF1558B1F5" name="打回修改" sourceRef="sid-E4B59163-B3E3-47D2-94C2-D079D90F2673" targetRef="modification">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result!=-999}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-A82C78B3-A7A3-434F-ABC4-1E624D6A0F25" sourceRef="modification" targetRef="sid-801B443D-1EE6-4B3D-A574-6939856E31BD"></sequenceFlow>
<sequenceFlow id="sid-550CC53D-C3B4-44E8-9456-74167659FAC0" name="取消审批" sourceRef="sid-E4B59163-B3E3-47D2-94C2-D079D90F2673" targetRef="cancelCEAAF">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==-999}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-1DB6E9FA-8EBD-493A-A4A8-396C1CD626AD" name="从初审开始重批" sourceRef="sid-801B443D-1EE6-4B3D-A574-6939856E31BD" targetRef="examAndApprove">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result!=-999}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-3A6B227E-0B82-4E4D-854B-39C9E081828B" name="N次循环(多个审批人/处理人、多次修改重批)" sourceRef="examAndApprove" targetRef="checkApproveResult"></sequenceFlow>
<sequenceFlow id="sid-069CD520-EA9F-4FD9-B20B-C1A6A335861A" name="取消审批" sourceRef="sid-801B443D-1EE6-4B3D-A574-6939856E31BD" targetRef="cancelCEAAF">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==-999}]]></conditionExpression>
</sequenceFlow>
<textAnnotation id="sid-9CA67D24-B4CE-4E6E-9597-E253BF9BEA43">
<text>只要审批没有通过,不管当前流程进行到哪个任务节点,申请人都可以取消审批(撤单)</text>
</textAnnotation>
<association id="sid-58C0FC70-A1CF-433D-BA68-737C39E47007" sourceRef="sid-E4B59163-B3E3-47D2-94C2-D079D90F2673" targetRef="sid-9CA67D24-B4CE-4E6E-9597-E253BF9BEA43" associationDirection="None"></association>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_CommonExamineAndApproveFlow">
<bpmndi:BPMNPlane bpmnElement="CommonExamineAndApproveFlow" id="BPMNPlane_CommonExamineAndApproveFlow">
<bpmndi:BPMNShape bpmnElement="startCEAAF" id="BPMNShape_startCEAAF">
<omgdc:Bounds height="30.0" width="29.999999999999986" x="119.00000000000001" y="180.00000000000026"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="examAndApprove" id="BPMNShape_examAndApprove">
<omgdc:Bounds height="77.0" width="110.99999999999997" x="240.00000000000003" y="156.50000000000023"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="modification" id="BPMNShape_modification">
<omgdc:Bounds height="80.0" width="106.99999999999994" x="405.00000000000006" y="326.11111140545563"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-801B443D-1EE6-4B3D-A574-6939856E31BD" id="BPMNShape_sid-801B443D-1EE6-4B3D-A574-6939856E31BD">
<omgdc:Bounds height="40.0" width="40.0" x="275.50000000000006" y="346.11111140545563"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E4B59163-B3E3-47D2-94C2-D079D90F2673" id="BPMNShape_sid-E4B59163-B3E3-47D2-94C2-D079D90F2673">
<omgdc:Bounds height="40.0" width="40.0" x="645.0000000000002" y="346.11111140545563"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="checkApproveResult" id="BPMNShape_checkApproveResult">
<omgdc:Bounds height="40.0" width="40.0" x="645.0000000000001" y="175.00000000000037"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endCEAAF" id="BPMNShape_endCEAAF">
<omgdc:Bounds height="28.0" width="28.0" x="840.0000000000002" y="181.00000000000043"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="cancelCEAAF" id="BPMNShape_cancelCEAAF">
<omgdc:Bounds height="28.0" width="27.999999999999986" x="120.00000000000001" y="352.1111114054557"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-9CA67D24-B4CE-4E6E-9597-E253BF9BEA43" id="BPMNShape_sid-9CA67D24-B4CE-4E6E-9597-E253BF9BEA43">
<omgdc:Bounds height="62.0" width="142.0" x="780.0000000000001" y="335.1111114054557"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-931CBA3F-5300-4C16-98BB-73AF1558B1F5" id="BPMNEdge_sid-931CBA3F-5300-4C16-98BB-73AF1558B1F5" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="53.49999999999997" flowable:targetDockerY="40.0">
<omgdi:waypoint x="645.0000000000002" y="366.11111140545563"></omgdi:waypoint>
<omgdi:waypoint x="511.95000000000005" y="366.11111140545563"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-550CC53D-C3B4-44E8-9456-74167659FAC0" id="BPMNEdge_sid-550CC53D-C3B4-44E8-9456-74167659FAC0" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="13.999999999999993" flowable:targetDockerY="14.0">
<omgdi:waypoint x="665.0000000000002" y="386.0502677044908"></omgdi:waypoint>
<omgdi:waypoint x="665.0000000000002" y="458.0"></omgdi:waypoint>
<omgdi:waypoint x="339.0" y="458.0"></omgdi:waypoint>
<omgdi:waypoint x="146.72314379197059" y="371.8184580073399"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-C21B362D-DC54-4507-B9C8-511450686938" id="BPMNEdge_sid-C21B362D-DC54-4507-B9C8-511450686938" flowable:sourceDockerX="14.999999999999991" flowable:sourceDockerY="15.0" flowable:targetDockerX="55.499999999999986" flowable:targetDockerY="38.5">
<omgdi:waypoint x="148.94999929925845" y="195.00000000000026"></omgdi:waypoint>
<omgdi:waypoint x="240.00000000000003" y="195.00000000000023"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-54D272DB-EBF8-4944-914B-69D77C3E89D1" id="BPMNEdge_sid-54D272DB-EBF8-4944-914B-69D77C3E89D1" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="684.9447250132206" y="195.0000000000004"></omgdi:waypoint>
<omgdi:waypoint x="840.0000000000002" y="195.00000000000043"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-069CD520-EA9F-4FD9-B20B-C1A6A335861A" id="BPMNEdge_sid-069CD520-EA9F-4FD9-B20B-C1A6A335861A" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="13.999999999999993" flowable:targetDockerY="14.0">
<omgdi:waypoint x="275.5000000000001" y="366.1111114054557"></omgdi:waypoint>
<omgdi:waypoint x="147.9499267462589" y="366.1111114054557"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-3A6B227E-0B82-4E4D-854B-39C9E081828B" id="BPMNEdge_sid-3A6B227E-0B82-4E4D-854B-39C9E081828B" flowable:sourceDockerX="55.499999999999986" flowable:sourceDockerY="38.5" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="350.9499999999042" y="195.00000000000026"></omgdi:waypoint>
<omgdi:waypoint x="645.0000000000001" y="195.00000000000037"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-58C0FC70-A1CF-433D-BA68-737C39E47007" id="BPMNEdge_sid-58C0FC70-A1CF-433D-BA68-737C39E47007" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="0.0" flowable:targetDockerY="31.0">
<omgdi:waypoint x="684.5247370727237" y="366.52777807212226"></omgdi:waypoint>
<omgdi:waypoint x="780.0000000000001" y="366.1111114054557"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-1DB6E9FA-8EBD-493A-A4A8-396C1CD626AD" id="BPMNEdge_sid-1DB6E9FA-8EBD-493A-A4A8-396C1CD626AD" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="55.499999999999986" flowable:targetDockerY="38.5">
<omgdi:waypoint x="295.50000000000006" y="346.11111140545563"></omgdi:waypoint>
<omgdi:waypoint x="295.5" y="233.45000000000024"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-39DAAB85-AF7E-4D7A-A4F3-66226719D9AC" id="BPMNEdge_sid-39DAAB85-AF7E-4D7A-A4F3-66226719D9AC" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="665.0000000000001" y="214.94417385944323"></omgdi:waypoint>
<omgdi:waypoint x="665.0000000000002" y="346.11111140545563"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-A82C78B3-A7A3-434F-ABC4-1E624D6A0F25" id="BPMNEdge_sid-A82C78B3-A7A3-434F-ABC4-1E624D6A0F25" flowable:sourceDockerX="53.49999999999997" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="404.9999999998836" y="366.11111140545563"></omgdi:waypoint>
<omgdi:waypoint x="315.40609269471895" y="366.11111140545563"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-94CE2A65-C720-4B6B-BCE4-363269AAB6A0" id="BPMNEdge_sid-94CE2A65-C720-4B6B-BCE4-363269AAB6A0" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="55.499999999999986" flowable:targetDockerY="38.5">
<omgdi:waypoint x="665.0000000000001" y="175.00000000000037"></omgdi:waypoint>
<omgdi:waypoint x="665.0000000000001" y="67.0"></omgdi:waypoint>
<omgdi:waypoint x="295.5" y="67.0"></omgdi:waypoint>
<omgdi:waypoint x="295.5" y="156.50000000000023"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
2.业务扩展数据库设计
- 增加work_flow_def表,用来存我们自己的流程名称
- 增加work_flow_form_field表,用来存我们定义的表单字段(不使用flowable的)
- 增加work_order表,用于存放发起流程关联的工单数据(一个流程实例对应一条work_order数据记录)
- 增加wo_assignee表,用来存放审批人设置的信息。
wo_assignee(工单处理人表,即流程模板和流程实例数据表)**
用来将业务流程转化为"通用审批流程",
其中字段**assignee(审批人)**可能是**具体的用户ID**,也可能是**变量**;
当一个流程中的某一个节点的assignee(审批人)是**变量时**,
那就需要在**创建流程实例前赋值**成具体的用户ID,
创建流程实例前会生成**工单ID(work_order表的order_id)**,
我们就用**(order_id和具体的用户ID)**来再存入wo_assignee表,
得到新的工单处理人数据,流程流转时就用这些数据,因此,这些数据就是流程实例数据
3.流转说明图
4. 测试说明
因代码有点多,因此这里只是展示下效果,详见可以下载文章最后提供的源码,使用springboot工程,只需要将工程下的dbscripts\flowabletest.sql导入数据库
,然后启动工程开箱即用,就可以进行测试
/**
* 通用的流程设计查看(不要删除):http://localhost:8080/flowabletest/modeler/#/processes/c1ad50ed-c7e2-11ec-8b32-02004c4f4f50
* @author jxlhl
* 测试URL:先从普通人员页面开始测试即可,即:http://localhost:8080/flowabletest/main.html?userId=lix
* 普通人员:http://localhost:8080/flowtest/main.html?userId=lix
* 项目经理:http://localhost:8080/flowtest/main.html?userId=zhaoyl
* 部门经理:http://localhost:8080/flowtest/main.html?userId=linjh
* 总经理:http://localhost:8080/flowtest/main.html?userId=xuw
*/
首页
流程表单设计和审批人设置
开启流程
审批
五、flowable和mybatis同时存在时问题解决
同时存在时不能正常启动,报存在2个session如类下以下错误
Description:
file [xxxx\com\carshow\flowable\mapper\IFlowableCommentMapper.class] required a single bean, but 2 were found:
- sqlSessionFactory: defined by method 'sqlSessionFactory' in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]
- modelerSqlSessionFactory: defined by method 'modelerSqlSessionFactory' in class path resource [org/flowable/ui/modeler/conf/ModelerDatabaseConfiguration.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
有博主的文章解决方法如https://blog.csdn.net/forever_xw_/article/details/122377609,我没有试过,觉得挺麻烦的,我这边直接采用手工配置多数据源,然后在写mybatis的mapper时指定使用的数据库来解决的,比如:
为数据库指定一个mybatis相关的config,如DefaultDbConfig
,指定此库相关的表的mapper使用的注解为UsingDefalutDB,然后在mapper的编写时也同时指定注解。
package cn.gzsendi.config.mybatis;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 指定Mapper使用defaultDB库
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UsingDefaultDB {
}
写mapper时指定注解,使用哪个数据库
详细可以看我的源码里面的代码
六、源码下载及说明
最后提供源码如下及说明:
总的代码地址
github: https://github.com/jxlhljh/flowabletest
gitee: https://gitee.com/jxlhljh/flowabletest.git
springboot整合Flowable6.7.2
https://github.com/jxlhljh/flowabletest/tree/master/v1/flowabletest
https://gitee.com/jxlhljh/flowabletest/tree/master/v1/flowabletest
Spring Boot 整合 Flowable-ui-modeler 6.7.2
https://github.com/jxlhljh/flowabletest/tree/master/v2/flowabletest
https://gitee.com/jxlhljh/flowabletest/tree/master/v2/flowabletest
整合完成后的通用审批流程的应用实践的代码
https://github.com/jxlhljh/flowabletest/tree/master/v3/flowabletest
https://gitee.com/jxlhljh/flowabletest/tree/master/v3/flowabletest