一、SringBoot整合Flowable
1.引入依赖
SpringBoot使用2.7.1,亲测3.3.0不能用,JDK使用1.8
建议slf4j版本如下,不会报错,太高了报错
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
<!--关闭自带的权限认证-->
<exclusions>
<exclusion>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-security</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0</version>
</dependency>
2.安装流程图绘制插件
Flowable BPMN visualizer
3.yml配置
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#nullCatalogMeansCurrent=true 设置为只查当前连接的schema库
url: jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
flowable:
#关闭定时任务
async-executor-activate: true
#数据库表与flowable最新表不一致会进行更新
database-schema-update: true
logging:
level:
org:
flowable: debug
二、Spring环境下的应用
1.流程部署与启动
@Autowired
ProcessEngine processEngine;
@Autowired
RepositoryService repositoryService;
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Test
void deployFlow(){
//流程引擎的配置对象,关联相关数据源
Deployment deploy = repositoryService.createDeployment()
//一次部署所有processes文件夹内的流程
.name("第一次部署")
.deploy();
System.out.println("deploy.getId()="+deploy.getId());
}
/**
* 启动流程实例
* 在流程定义表中动态维护 act_re_procdef
*/
@Test
void startFlow(){
String processId="ask_for_leave.bpmn20:1:4";
String processKey="ask_for_leave.bpmn20";
//1.根据流程定义di启动流程实例
runtimeService.startProcessInstanceById(processId);
//2.根据流程定义key启动流程实例
//runtimeService.startProcessInstanceByKey(processKey);
}
/**
* 任务的审批
* 需要数据:任务id
*/
@Test
void compeleteTask(){
taskService.complete("2506");
}
- 每启动一个流程,会在act_hi_procinst中维护一条数据。
- 启动一个流程,可以在act_ru_task表中看到对应的记录,该表记录的都是当前待办的记录信息。
- act_ru_execution记录流程的分支
流程定义:相当于Java中的类
流程实例:相当于java中的对象
注意:在Spring环境下,Spring会自动扫描processes文件夹,若不指定文件路径,则一次把所有bpmn流程全部部署,也就是一次创建所有流程定义。一个部署对应多个流程定义
@Test
void deployFlow(){
Deployment deploy = repositoryService.createDeployment()
//如果再添加部署文件,会部署两次,导致流程定义中出现新版本的流程定义
.addClasspathResource("processes/ask_for_leave.bpmn20.xml")
.name("部署名称")
.deploy();
System.out.println("deploy.getId()="+deploy.getId());
}
常用:1.通过bpmn部署。2.通过zip压缩包部署。3.自己创建model模型,然后保存到数据库中,再通过模型部署。
创建好model之后保存到act_re_model表中(上图仅仅是举个3的例子)。部署也是先从表中获取model部署。
2.表结构
- act_re: repository,包含流程定义和流程静态资源(图片,规则等)
- act_ru: runtime,运行时的表,包含实例,任务,变量,异步任务,运行中的数据等
- act_hi:history,流程的历史数据,如历史流程实例,变量,任务。
- act_ge:general,通用数据。act_ge_bytearray(重要),存放了流程定义的png图片。
- act_id:identity组织机构。包含标识的信息,如用户,用户组等。
- CMMN 表示这都是跟 CMMN 协议相关的表。
- CO(CONTENT)表示这都是跟内容引擎相关的表。
- DMN 表示这都是跟 DMN 协议相关的表。
- FO(FORM)表示这都是跟表单相关的表。
3.流程挂起和激活
/**
* 流程定义的挂起和激活
*act_re_procdef
*/
@Test
void suspendedActivity(){
String processDefinitionId="ask_for_leave.bpmn20:1:4";
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
//获取当前流程定义的状态
boolean suspended = processDefinition.isSuspended();
if (suspended) {
//挂起-->激活
System.out.println("激活流程");
repositoryService.activateProcessDefinitionById(processDefinitionId);
}else {
//激活-->挂起
System.out.println("挂起流程");
repositoryService.suspendProcessDefinitionById(processDefinitionId);
}
}
- 已经挂起的流程定义,不允许启动。
- 已经启动的流程,不受影响。
- 可以挂起已经启动的流程实例,该流程实例不允许审批。
4.任务分配表达式Assignee
Assignee也可以写表达式
- 值表达式${ assign1 },assign1是自己定义的变量。
- 方法表达式${ myBean.getAssignee() }
/**
* 任务的审批
* 需要数据:任务id,hashMap "assign1","lisi"
* 把当前任务分给lisi审批,运行后lisi可以查到待办
*/
@Test
void compeleteAssign1(){
HashMap<String, Object> variables = new HashMap<>();
variables.put("assign1","lisi");
//完成任务审批,根据任务id绑定对应表达式的值
taskService.complete("taskId",variables);
}
(act_ru_actinst)出现了lisi
@Test
void findFlow(){
//任务实例通过TaskService来实现
TaskService taskService = getEngine().getTaskService();
//获取到 act_ru_task中 assignee是lisi的记录
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("lisi")
.list();
tasks.forEach(System.out::println);
//Task[id=15004, name=second]
}
得到结果Task[id=15004, name=second],此时还需要审批。
------ 方法表达式${ myBean.getAssignee() } -------
//MyBean加入容器
@Component
public class MyBean {
public String getAssignee(){
System.out.println("getAssignee执行...");
return "王五";
}
}
@Test
void compeleteAssign1(){
TaskService taskService = getEngine().getTaskService();
//再执行审批时,会去MyBean中执行该方法
taskService.complete("15004");
}
拿到id后再执行审批。
5.流程变量
- 运行时变量全局变量局部变量
- 历史变量
@Test void startFlow(){ String processId="firstFlow:2:637173cf-1ce6-11ef-8399-005056c00008"; //在流程启动时就可以绑定对应表达式的值,因为第一个人就需要指定人 Map<String,Object> variables =new HashMap<>(); variables.put("var1","test1"); variables.put("var2","test2"); variables.put("var3","test3"); //全局变量存在了act_ru_variable runtimeService.startProcessInstanceById(processId,variables); } /** * 获取流程全局变量 */ @Test //全局变量存在了act_ru_variable void getVariables(){ String execution ="06a934bf-1ce7-11ef-870f-005056c00008"; //还能直接设置 //runtimeService.setVariable(execution,"var4","test4"); //设置局部变量,和taskId相关,节点没了变量就没了 //runtimeService.setVariablesLocal(execution,"var4","test4"); Map<String, Object> variables = runtimeService.getVariables(execution); System.out.println(variables); }
输出{var3=test3, var2=test2, var1=test1}
6.候选人
可以指定多个候选人,在启动流程时进行赋值就好。
/**
* 根据候选人查询任务
* 候选人需要拾取任务才能变成审批人
*只有一个人能变为审批人,审批人还可以归还,变成候选人
*/
@Test
void claimTask(){
//act_ru_task中
List<Task> tasks = taskService.createTaskQuery()
//这里有改变
.taskCandidateUser("张三")
.list();
for (Task task : tasks) {
//拾取
taskService.claim(task.getId(),"张三");
//归还unclaim(task.getId(),"张三")
//指派taskService.setAssignee(task.getId(),"xxx")
}
}
@Test
void findFlow(){
//act_ru_task中
List<Task> tasks = taskService.createTaskQuery()
//这里有改变
.taskCandidateUser("张三")
.list();
tasks.forEach(System.out::println);
}
7.候选人组
//先创建用户
@Test
void createUser(){
User user = identityService.newUser("zhangsan");
user.setEmail("[email protected]");
user.setFirstName("zhang");
user.setLastName("san");
user.setPassword("123456");
identityService.saveUser(user);
}
/**
* 用户组
*/
@Test
void createGroup(){
Group group = identityService.newGroup("xsb");
group.setName("销售部");
group.setType("type1");
identityService.saveGroup(group);
}
/**
* 用户与用户组的关系
*/
@Test
void createMemberShip(){
Group group = identityService.createGroupQuery().groupId("xsb").singleResult();
List<User> users = identityService.createUserQuery().list();
users.forEach(user -> {identityService.createMembership(user.getId(), group.getId());});
}
act_id_group
act_id_membership
直接部署后启动,act_ru_identitylink中就会出现候选组信息。
/**
* 当前登录用户根据候选人组查询任务
*/
@Test
void findGroupTask(){
//先查询当前所在的组 如查张三
Group group = identityService.createGroupQuery()
.groupMember("zhangsan").singleResult();
System.out.println("当前用户所在组的id为:"+group.getId());
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(group.getId()).list();
for (Task task : tasks) {
//拾取任务
taskService.claim(task.getId(),"zhangsan");
}
}
/**
* 任务的审批
*/
@Test
void compeleteTask(){
Map<String,Object> variables =new HashMap<>();
taskService.complete("ae543ec1-1d5f-11ef-a409-005056c00008");
}
8.网关
- 排他网关
- 并行网关
- 包容网关
- 事件网关
排他网关
在审批的时候加入day天数就能完成请假步骤,从而转向不同的审批人。
@Test
void compeleteTask(){
Map<String,Object> variables =new HashMap<>();
variables.put("day",3);
taskService.complete("fae74a9a-1d6c-11ef-9a09-005056c00008");
}
注意:条件尽量包含所有情况,否则报错。
并行网关
提交申请之后,task表中将会出现两条审批,分别是zhangsan和lisi。
包含网关
可以看成并行和排他的结合体
如num=2走三条,num=5走两条,num=8走三条
Tips
- 每次重新部署一个id相同的bpmn时,正在执行的流程会被自动删除。
- 流程定义与流程部署是不一样的,一个流程部署可以对应多个流程定义。流程部署的表是act_re_deployment,流程定义的表是act_re_procdef。可以理解为act_re_procdef是act_re_deployment的从表。
- 流程定义不需要删除,当你不要这个流程定义时,就把流程部署给删除好了。
- 删除流程部署,默认级联删除,正在执行的流程实例也会被删除。
与Activiti7的区别
1.Activiti默认不开启数据库的历史记录,flowable默认开启
2.Activiti23张表,flowable79张表。
3.Flowable是Activiti的继任者,因此Flowable包含了Activiti的所有功能,并且在原有功能的基础上进行了进一步的改进和优化。
4.支持 CMMN 和 DMN 标准
三、SpringBoot项目中引入flowable
1.配置flowable独立数据源
flowable:
async-executor-activate: false
#第一次生成后关闭
database-schema-update: true
#保存历史数据级别
history-level: full
#解决乱码
activity-font-name: "宋体"
annotation-font-name: "宋体"
label-font-name: "宋体"
#配置flowable数据源
flow:
username: root
password: root
url: jdbc:mysql://localhost:3306/flowable2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
maxPoolSize: 30
2.创建配置类
import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.app.spring.SpringAppEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
/**
* 一共有两个配置类,一个是ProcessEngineConfiguration,一个是SpringAppEngineConfiguration
* 他们里面都需要重写configure方法来进行配置
* 配置数据源应该在SpringAppEngineConfiguration中设定
* 前者是配置ProcessEngine的,如自动生成表,设置中文,在yml文件中配置的属性便是在此类中读取
*/
@Configuration
@PropertySource("classpath:application-dev.yml")
@Slf4j
public class FlowableConfig implements EngineConfigurationConfigurer<SpringAppEngineConfiguration> {
//读取配置
@Value("${flow.username}")
private String user;
@Value("${flow.password}")
String password;
@Value("${flow.url}")
String jdbcUrl;
@Value("${flow.driver-class-name}")
String driverClass;
@Value("${flow.maxPoolSize}")
int maxPoolSize;
// @Bean(name = "processEngine")
// public ProcessEngine processEngineConfiguration() throws PropertyVetoException {
// ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration();
// cfg.setDataSource(dataSource2());
// cfg.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE);
// cfg.setActivityFontName("宋体");
// cfg.setLabelFontName("宋体");
// cfg.setAnnotationFontName("宋体");
// cfg.setAsyncExecutorActivate(false);
// return cfg.buildProcessEngine();
// }
//配置数据源
public DataSource dataSource2() throws PropertyVetoException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl(jdbcUrl);
dataSource.setDriverClassName(driverClass);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolSize);
return dataSource;
}
@Override
public void configure(SpringAppEngineConfiguration engineConfiguration) {
try {
//把数据源设置进来
engineConfiguration.setDataSource(dataSource2());
log.info("配置flowable数据源成功");
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
注意这里配置的是SpringAppEngineConfiguration,而不是ProcessEngine,否则将出现报错,或者设置单独的数据源失败。
项目启动成功就可以看到自动创建的79张表了。然后把表的自动更新关闭,否则会影响性能。
版权归原作者 C和弦与炊烟 所有, 如有侵权,请联系我们删除。