1. 介绍
Flowable是BPMN的一个基于Java的软件实现,但是不仅仅限于BPMN,还有DMN决策表和CMMN Case管理引擎,并且有自己的用户管理,微服务API的功能,是一个服务平台。
是由开发了Acitivity6的开发人员,再次升级开发的。
2. 项目搭建
2.1 初始化
- 构建一个普通maven项目。
- 引入依赖:
<dependencies>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</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.21</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
</dependencies>
- 新建测试类。
@Test
public void testProcessEngine(){
// 流程配置对象
ProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();
// 数据库配置
configuration.setJdbcDriver("com.mysql.jdbc.Driver");
configuration.setJdbcUsername("root");
configuration.setJdbcPassword("root");
configuration.setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8");
// 配置如果表结构不存在就新建
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
// 构造核心流程对象
ProcessEngine processEngine = configuration.buildProcessEngine();
System.err.println(processEngine);
}
- 耐心等待运行(创建表结构)
- 配置slf4j,在
resource
下新建配置文件log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/logFile.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 配置logback,resource下新建logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="APP_NAME" value="MY_APP_NAME" />
<property name="LOG_DIR" value="logs" />
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %boldYellow([%thread]) %cyan(%logger{15}) %msg%n"/>
<contextName>${APP_NAME}</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_DIR}/logFile.log</file>
<append>true</append>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/dayLogFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 使用root的appender-ref -->
<logger name="com.example.Logger1" level="DEBUG" additivity="true">
</logger>
<!-- 不使用root的appender-ref -->
<logger name="com.example.Logger2" level="DEBUG" additivity="false">
</logger>
<logger name="com.example.Logger3" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="RollingFile" />
</root>
</configuration>
- 再次启动测试,可以看到是彩色的日志
3. 部署流程测试
使用官方给的请假测试流程。
3.1 流程图
-
流程图如下:
- 发起请求之后由管理员去审批:通过或者拒绝
- 通过/拒绝,之后触发不同的流程。
-
流程图说明
- 左侧的圆圈叫做启动事件(start event)。这是一个流程实例的起点。
- 第一个矩形是一个用户任务(user task)。这是流程中用户操作的步骤。在这个例子中,管理员需要批准或驳回申请。
- 取决于管理员的决定,排他网关(exclusive gateway) (带叉的菱形)会将流程实例路由至批准或驳回路径。
- 如果批准,则需要将申请注册至某个外部系统,并跟着另一个用户任务,将经理的决定通知给申请人。当然也可以改为发送邮件。
- 如果驳回,则为雇员发送一封邮件通知他。
3.2 流程文件
- 对应的xml文件:将xml放到
resource
文件夹下,并命名为holiday-request.bpmn20.xml
<?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: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"
xmlns:flowable="http://flowable.org/bpmn"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">
<process id="holidayRequest" name="Holiday Request" isExecutable="true">
<!-- 开始-->
<startEvent id="startEvent"/>
<!-- 来源:startEvent 目的地:approveTask-->
<sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>
<!-- 用户任务-->
<userTask id="approveTask" name="Approve or reject request"/>
<!-- 用户任务目的地排他网关-->
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
<!-- 排他网关-->
<exclusiveGateway id="decision"/>
<!-- 排他网关目标1-->
<sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<!-- 排他网关目标2-->
<sequenceFlow sourceRef="decision" targetRef="sendRejectionMail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
<!-- 同意流程节点-->
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
flowable:class="org.flowable.CallExternalSystemDelegate"/>
<sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>
<!-- 同意之后的节点-->
<userTask id="holidayApprovedTask" name="Holiday approved"/>
<!-- 通过-指向结束-->
<sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>
<!-- 拒绝流程让任务节点-->
<serviceTask id="sendRejectionMail" name="Send out rejection email"
flowable:class="org.flowable.SendRejectionMail"/>
<!-- 拒绝-指向结束-->
<sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>
<endEvent id="approveEnd"/>
<endEvent id="rejectEnd"/>
</process>
</definitions>
3.3 部署文件
- 新建单元测试文件
public class DoFlowable {
ProcessEngineConfiguration configuration = null;
/**
* 获取flowable流引擎对象
*/
@Before
public void testProcessEngine(){
// 流程配置对象
configuration = new StandaloneProcessEngineConfiguration();
// 数据库配置
configuration.setJdbcDriver("com.mysql.jdbc.Driver");
configuration.setJdbcUsername("root");
configuration.setJdbcPassword("root");
configuration.setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8");
// 配置如果表结构不存在就新建
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
}
/**
* 部署流程
*/
@Test
public void testDeploy(){
// 获取 processEngine 对象
ProcessEngine processEngine = configuration.buildProcessEngine();
// 获取 repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 获取 deployment 对象
Deployment deployment = repositoryService.createDeployment()// 创建Deployment对象
.addClasspathResource("holiday-request.bpmn20.xml") // 添加流程部署文件
.name("请求流程") // 设置部署流程的名称
.deploy(); // 执行
System.err.println("deployment.getId() = " + deployment.getId());
System.out.println("deployment.getName() = " + deployment.getName());
}
}
- 执行之后:
- 查看数据库:可以看到数据
4. 流程API
- 先封装一个工具类,方便用来获取
ProcessEngine
对象
public class ProcessEngineUtils {
/**
* 获取flowable流引擎对象
*/
public static ProcessEngine getProcessEngine(){
// 流程配置对象
ProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();
// 数据库配置
configuration.setJdbcDriver("com.mysql.jdbc.Driver");
configuration.setJdbcUsername("root");
configuration.setJdbcPassword("root");
configuration.setJdbcUrl("jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC&nullCatalogMeansCurrent=true&useSSL=true");
// 配置如果表结构不存在就新建
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
// 构造核心流程对象
return configuration.buildProcessEngine();
}
}
4.1 查询操作
/**
* 查询流程定义信息
*/
@Test
public void deploymentQuery(){
ProcessEngine processEngine = ProcessEngineUtils.getProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
// 获取查询对象
ProcessDefinitionQuery processQuery = repositoryService.createProcessDefinitionQuery();
// 定义查询信息(单个查询)
ProcessDefinition definition = processQuery.deploymentId("2501").singleResult();
}
4.2 删除操作
/**
* 删除流程定义
*/
@Test
public void deployDel(){
ProcessEngine processEngine = ProcessEngineUtils.getProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.deleteDeployment("1"); // 删除对应id的流程,启动中不可以删除
// repositoryService.deleteDeployment("1", true); // 删除对应id的流程(包含其中的过程数据),不考虑启动状态。
}
4.3 启动流程
/**
* 启动流程
*/
@Test
public void deployRun(){
// 配置传递的参数
Map<String, Object> params = new HashMap<>();
params.put("employee","王五");
params.put("days",3);
params.put("description","累了");
ProcessEngine processEngine = ProcessEngineUtils.getProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 1:这里的 key 对应配置文件中的 process 的 id
// 2:传递的参数
ProcessInstance holidayRequest = runtimeService.startProcessInstanceByKey("holidayRequest", params);
}
-
查看传入的数据
-
查看启动的流程
-
查看执行进度
4.4 管理员查询
- 修改xml中的审批处理人
<!-- 用户任务-->
<userTask id="approveTask" name="Approve or reject request" flowable:assignee="Ekko"/>
<!-- 用户任务目的地排他网关-->
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
- 使用删除api,第二个参数传入
true
,级联删除任务数据。 - 重新部署流程。
- 再次重新启动流程。可以看到我们指定的审批人为:Ekko。
- 查询代码
/**
* 任务查询
*/
@Test
public void taskQuery(){
ProcessEngine processEngine = ProcessEngineUtils.getProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("holidayRequest") // 指定流程key
.taskAssignee("Ekko") // 指定处理人
.list();
for (Task task : list) {
System.out.println(task.getProcessDefinitionId());
System.out.println(task.getName());
System.out.println(task.getAssignee());
System.out.println(task.getDescription());
System.out.println(task.getId());
}
}
4.5 管理员审批
- 修改配置文件中排他网关的两种处理走的类这里的配置修改之后需要重新部署一下流程。
<!-- 同意流程节点-->
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
flowable:class="com.yy.flowable.service.AppService"/>
<sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>
<!-- 拒绝流程让任务节点-->
<serviceTask id="sendRejectionMail" name="Send out rejection email"
flowable:class="com.yy.flowable.service.RejectService"/>
<!-- 拒绝-指向结束-->
<sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>
- 新增流程处理类
- 请假通过接口
/**
* @author : JinMing Zhou
* @description: 通过类
* @date : 2023/1/9 17:48
*/
public class AppService implements JavaDelegate {
/**
* 触发器,流程走到这里执行该方法
* @param delegateExecution
*/
@Override
public void execute(DelegateExecution delegateExecution) {
// 通过,执行逻辑
System.out.println("通过。");
}
}
- 请假拒绝接口
/**
* @author : JinMing Zhou
* @description: 拒绝类
* @date : 2023/1/9 17:48
*/
public class RejectService implements JavaDelegate {
/**
* 请假流程拒绝触发器
* @param delegateExecution
*/
@Override
public void execute(DelegateExecution delegateExecution) {
System.err.println("不给通过!");
}
}
- 处理操作
/**
* 处理流程
*/
@Test
public void completeTask(){
// 指定审批参数
Map<String, Object> params = new HashMap<>();
params.put("approved",false);
ProcessEngine processEngine = ProcessEngineUtils.getProcessEngine();
TaskService taskService = processEngine.getTaskService();
// 查询任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("holidayRequest") // 指定流程key
.taskAssignee("Ekko") // 指定处理人
.singleResult();
// 处理操作
taskService.complete(task.getId(), params);
}
4.6 历史记录查询
/**
* 历史信息查询
*/
@Test
public void historyQuery(){
ProcessEngine processEngine = ProcessEngineUtils.getProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processDefinitionId("holidayRequest:1:17503")
.finished()
.orderByHistoricActivityInstanceEndTime().asc()
.list();
for (HistoricActivityInstance historicActivityInstance : list) {
System.err.println(historicActivityInstance.getActivityId() + " took "
+ historicActivityInstance.getDurationInMillis() + " milliseconds");
}
}