SpringBoot 工作流引擎初体验 原创
Flowable是什么
Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。这个章节将用一个可以在你自己的开发环境中使用的例子,逐步介绍各种概念与API。
Flowable可以十分灵活地加入你的应用/服务/构架。可以将JAR形式发布的Flowable库加入应用或服务,来嵌入引擎。 以JAR形式发布使Flowable可以轻易加入任何Java环境:Java SE;Tomcat、Jetty或Spring之类的servlet容器;JBoss或WebSphere之类的Java EE服务器,等等。 另外,也可以使用Flowable REST API进行HTTP调用。也有许多Flowable应用(Flowable Modeler, Flowable Admin, Flowable IDM 与 Flowable Task),提供了直接可用的UI示例,可以使用流程与任务。
Flowable命名规则:
ACT_RE_* :’ RE ’表示repository(存储)。RepositoryService接口操作的表。带此前缀的表包含的是静态信息,如,流程定义,流程的资源(图片,规则等)。
ACT_RU_* :’ RU ’表示runtime。这是运行时的表存储着流程变量,用户任务,变量,职责(job)等运行时的数据。flowable只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
ACT_ID_* : ’ ID ’表示identity(组织机构)。这些表包含标识的信息,如用户,用户组,等等。
ACT_HI_* : ’ HI ’表示history。就是这些表包含着历史的相关数据,如结束的流程实例,变量,任务,等等。
ACT_GE_* : 普通数据,各种情况都使用的数据
注意:Springboot 启动后表会自动创建
34张表说明
表名 | 表说明 |
---|---|
ACT_GE_BYTEARRAY | 通用的流程定义和流程资源 |
ACT_GE_PROPERTY | 系统相关属性 |
ACT_HI_ACTINST | 历史的流程实例 |
ACT_HI_ATTACHMENT | 历史的流程附件 |
ACT_HI_COMMENT | 历史的说明性信息 |
ACT_HI_DETAIL | 历史的流程运行中的细节信息 |
ACT_HI_IDENTITYLINK | 历史的流程运行过程中用户关系 |
ACT_HI_PROCINST | 历史的流程实例 |
ACT_HI_TASKINST | 历史的任务实例 |
ACT_HI_VARINST | 历史的流程运行中的变量信息 |
ACT_ID_BYTEARRAY | 二进制数据表 |
ACT_ID_GROUP | 用户组信息表 |
ACT_ID_INFO | 用户信息详情表 |
ACT_ID_MEMBERSHIP | 人与组关系表 |
ACT_ID_PRIV | 权限表 |
ACT_ID_PRIV_MAPPING | 用户或组权限关系表 |
ACT_ID_PROPERTY | 属性表 |
ACT_ID_TOKEN | 系统登录日志表 |
ACT_ID_USER | 用户表 |
ACT_RE_DEPLOYMENT | 部署单元信息 |
ACT_RE_MODEL | 模型信息 |
ACT_RE_PROCDEF | 已部署的流程定义 |
ACT_RU_DEADLETTER_JOB | 正在运行的任务表 |
ACT_RU_EVENT_SUBSCR | 运行时事件 |
ACT_RU_EXECUTION | 运行时流程执行实例 |
ACT_RU_HISTORY_JOB | 历史作业表 |
ACT_RU_IDENTITYLINK | 运行时用户关系信息 |
ACT_RU_JOB | 运行时作业表 |
ACT_RU_SUSPENDED_JOB | 暂停作业表 |
ACT_RU_TASK | 运行时任务表 |
ACT_RU_TIMER_JOB | 定时作业表 |
ACT_RU_VARIABLE | 运行时变量表 |
ACT_EVT_LOG | 事件日志表 |
ACT_PROCDEF_INFO | 流程定义信息 |
BPMN 2.0
业务流程模型注解(Business Process Modeling Notation - BPMN)是 业务流程模型的一种标准图形注解。这个标准 是由对象管理组(Object Management Group - OMG)维护的。
基本上,BPMN规范定义了任务看起来怎样的,哪些结构可以 与其他进行连接,等等。这就意味着 意思不会被误解。
标准的早期版本(1.2版以及之前)仅仅限制在模型上, 目标是在所有的利益相关者之间形成通用的理解, 在文档,讨论和实现业务流程之上。 BPMN标准证明了它自己,现在市场上许多建模工具 都使用了BPMN标准中的元素和结构。 实际上,现在的jPDL设计器也使用了 BPMN元素。
BPMN规范的2.0版本,当前已经处于最终阶段了, 已经计划不就就会完成,允许添加精确的技术细节 在BPMN的图形和元素中, 同时制定BPMN元素的执行语法。 通过使用XML语言来指定业务流程的可执行语法, BPMN规范已经演变为业务流程的语言, 可以执行在任何兼容BPMN2的流程引擎中, 同时依然可以使用强大的图形注解。
使用BPMN2.0定义的工作流
BPMN2.0定义工作流的方式有很多种:
- idea下载 actibBPM插件完成BPMN2.0的定义,下载后新建一个.bpmn文件进行工作流的定义,但是有中文乱码存在不推荐
- Flowable官网开发了工作流设计器
工作流设计器:https://github.com/flowable/flowable-engine/releases/download/flowable-6.4.0/flowable-6.4.0.zip
下载后将flowable-6.4.0.zip进行解压;
把wars目录下的war包复制到你安装的Tomcat8.0中的webapps目录下。
启动Tomcat8.0通过http://localhost:8080/flowable-modeler进行访问
账号:admin/test
项目实战
-
创建流程
经历妥妥拽拽形成上图的工作流。 妥妥拽拽过程忽略 -
导入下载后的工作流文件
工作流文件为什么放在这个目录????
源码中规定了在processes下,并且规定了格式
- 配置数据源
server:
port: 8090
spring:
datasource:
url: jdbc:mysql://tlshopdb.com:33806/act5?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
flowable:
#关闭定时任务JOB
async-executor-activate: false
## 开启flowablesql打印
logging:
level:
org:
flowable:
ui:
modeler:
domain:
Model: debug
knife4j:
enable: true
- 编写ActController文件体验工作流
package com.jackgreek.actspringboot.controller;
import com.jackgreek.actspringboot.pojo.Evection;
import io.swagger.annotations.ApiOperation;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
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;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: jackgeeks
* @ProjectName: demo
* @Package: com.example.demo.controller
* @ClassName: ActController
* @Description: @todo
* @CreateDate: 2022/1/3 20:55
* @Version: 1.0
*/
@RestController
@RequestMapping("flowable")
public class ActController {
@Autowired
//提供了很多启动流程的API,并且全部的命名规则为startProcessInstanceByXX
private RuntimeService runtimeService;
@Autowired
//任务服务
private TaskService taskService;
@Autowired
//历史流程实例查询
private HistoryService historyService;
@Autowired
//流程图管理
private RepositoryService repositoryService;
@Autowired
private ProcessEngine processEngine;
@Autowired
//内置用户操作
private IdentityService identityService;
/**
* 创建流程
*
* @param userId
* @param days
* @return
*/
@GetMapping("addExpense")
@ApiOperation("创建流程")
public String addExpense(String userId, String days) {
identityService.setAuthenticatedUserId(userId);
// 设置流程变量
Evection evection = new Evection();
// 设置出差日期
evection.setNum(days);
// 把流程变量的pojo放入map
Map<String, Object> map = new HashMap<>();
map.put("evection", evection);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myLeave",map);
// 查询当前任务
Task currentTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
//taskService.setAssignee(currentTask.getId(),null);
// 申明任务
taskService.claim(currentTask.getId(), userId);
System.out.println(processInstance);
return "提交成功,流程ID为:" + processInstance.getId();
}
/**
* processDefinitionId
*
* @param processDefinitionId
* @return
*/
@GetMapping("QueryByProcessInstanceId")
@ApiOperation("根据流程实例ID查询待办任务")
public Object QueryByProcessInstanceId(String processDefinitionId) {
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processDefinitionId).list();
return tasks.toString();
}
/**
* 通过/拒绝任务
*
* @param taskId
* @param approved 1 :true 2:false
* @return
*/
@GetMapping("apply")
@ApiOperation("通过/拒绝任务")
public String apply(String taskId, String approved) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
return "流程不存在";
}
Map<String, Object> variables = new HashMap<>();
Boolean apply = approved.equals("1") ? true : false;
variables.put("approved", apply);
taskService.complete(taskId, variables);
return "审批是否通过:" + approved;
}
/**
* 查看历史流程记录
*
* @param processInstanceId
* @return
*/
@GetMapping("historyList")
@ApiOperation("查看历史流程记录")
public Object getHistoryList(String processInstanceId) {
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).finished().orderByHistoricActivityInstanceEndTime().asc().list();
for(HistoricActivityInstance hai:historicActivityInstances){
System.out.println("活动ID:"+hai.getId());
System.out.println("流程实例ID:"+hai.getProcessInstanceId());
System.out.println("活动名称:"+hai.getActivityName());
System.out.println("办理人:"+hai.getAssignee());
System.out.println("开始时间:"+hai.getStartTime());
System.out.println("结束时间:"+hai.getEndTime());
System.out.println("=================================");
}
return historicActivityInstances;
}
/**
* 驳回流程实例
*
* @param taskId
* @param targetTaskKey
* @return
*/
@GetMapping("rollbask")
public String rollbaskTask(String taskId, String targetTaskKey) {
Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
if (currentTask == null) {
return "节点不存在";
}
List<String> key = new ArrayList<>();
key.add(currentTask.getTaskDefinitionKey());
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId())
.moveActivityIdsToSingleActivityId(key, targetTaskKey)
.changeState();
return "驳回成功...";
}
/**
* 终止流程实例
*
* @param processInstanceId
*/
public String deleteProcessInstanceById(String processInstanceId) {
// ""这个参数本来可以写删除原因
runtimeService.deleteProcessInstance(processInstanceId, "");
return "终止流程实例成功";
}
/**
* 挂起流程实例
*
* @param processInstanceId 当前流程实例id
*/
@GetMapping("hangUp")
public String handUpProcessInstance(String processInstanceId) {
runtimeService.suspendProcessInstanceById(processInstanceId);
return "挂起流程成功...";
}
/**
* 恢复(唤醒)被挂起的流程实例
*
* @param processInstanceId 流程实例id
*/
@GetMapping("recovery")
public String activateProcessInstance(String processInstanceId) {
runtimeService.activateProcessInstanceById(processInstanceId);
return "恢复流程成功...";
}
/**
* 判断传入流程实例在运行中是否存在
*
* @param processInstanceId
* @return
*/
@GetMapping("isExist/running")
public Boolean isExistProcIntRunning(String processInstanceId) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (processInstance == null) {
return false;
}
return true;
}
/**
* 判断流程实例在历史记录中是否存在
* @param processInstanceId
* @return
*/
@GetMapping("isExist/history")
public Boolean isExistProcInHistory(String processInstanceId) {
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (historicProcessInstance == null) {
return false;
}
return true;
}
/**
* 我发起的流程实例列表
*
* @param userId
* @return 流程实例列表
*/
@GetMapping("myTasks")
public List<HistoricProcessInstance> getMyStartProcint(String userId) {
List<HistoricProcessInstance> list = historyService
.createHistoricProcessInstanceQuery()
.startedBy(userId)
.orderByProcessInstanceStartTime()
.asc()
.list();
return list;
}
/**
* 查询流程图
*
* @param httpServletResponse
* @param processId
* @throws Exception
*/
@RequestMapping(value = "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);
}
//获取流程图
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);
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();
}
}
}
}
- 启动项目,自动在数据库构建Flowable需要使用的32张表
访问Swagger:http://localhost:8090/doc体验工作流
![b9f3e1935834022cd7997926494e504]