flowable工作流
1、介绍
Flowable 是一个轻量级的业务流程引擎,基于 BPMN 2.0 规范实现。它可以帮助开发者将业务流程可视化,并提供了强大的流程控制和管理功能。Flowable 适用于各种业务场景,如审批流程、工单处理、订单履行等。
2、BPMN文件创建
IDEA中安装插件Flowable BPMN visualizer,通过该插件可以快速创建bpmn文件。
以请假流程为例

<?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">
<process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
<startEvent id="sid-c5a66567-d665-4139-9fbd-392741c61671"/>
<userTask id="sid-eb308e04-f77b-4576-bc43-161deda4abe9" name="组长审批" flowable:assignee="${zuzhangTask}"/>
<exclusiveGateway id="sid-9fe275b2-c910-4449-ba1a-227815280f1d"/>
<sequenceFlow id="sid-34745413-a80c-445a-93f3-f476f0ccb787" sourceRef="sid-eb308e04-f77b-4576-bc43-161deda4abe9" targetRef="sid-9fe275b2-c910-4449-ba1a-227815280f1d"/>
<userTask id="sid-ffb1f1ad-a139-47ee-9af8-4b19b14faa05" name="经理审批" flowable:assignee="${managerTask}"/>
<sequenceFlow id="sid-ed534be0-25ad-4b49-8254-142b429079f8" sourceRef="sid-9fe275b2-c910-4449-ba1a-227815280f1d" targetRef="sid-ffb1f1ad-a139-47ee-9af8-4b19b14faa05" name="通过">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='pass'}]]></conditionExpression>
</sequenceFlow>
<endEvent id="sid-e64c88c2-544d-4040-bda3-a4dfee23bbb0"/>
<sequenceFlow id="sid-db63e3b6-f3e6-4376-8163-a5132c5428b6" sourceRef="sid-9fe275b2-c910-4449-ba1a-227815280f1d" targetRef="sid-a4a38c66-7b97-4ed4-8de2-e13c6fed8b6f" name="拒绝">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='reject'}]]></conditionExpression>
</sequenceFlow>
<exclusiveGateway id="sid-8a49b07f-c358-4c22-aeea-ab20582159be"/>
<sequenceFlow id="sid-39e9567a-a55d-40e2-a6a9-d0965f143246" sourceRef="sid-ffb1f1ad-a139-47ee-9af8-4b19b14faa05" targetRef="sid-8a49b07f-c358-4c22-aeea-ab20582159be"/>
<endEvent id="sid-04fde03b-b8b9-407c-8719-47757478c318"/>
<sequenceFlow id="sid-85980f9d-128c-41c9-8e85-3b363f5e952d" sourceRef="sid-8a49b07f-c358-4c22-aeea-ab20582159be" targetRef="sid-04fde03b-b8b9-407c-8719-47757478c318" name="通过">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='pass'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-0c361f56-426a-4522-81c8-82bfe0f36578" sourceRef="sid-8a49b07f-c358-4c22-aeea-ab20582159be" targetRef="sid-a4a38c66-7b97-4ed4-8de2-e13c6fed8b6f">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='reject'}]]></conditionExpression>
</sequenceFlow>
<serviceTask id="sid-a4a38c66-7b97-4ed4-8de2-e13c6fed8b6f" flowable:exclusive="true" flowable:class="com.taoge.flowabledemo.task.EmailServiceTask" name="发送通知"/>
<sequenceFlow id="sid-bd7cd20d-3131-40a4-b4b8-72b5d597f89c" sourceRef="sid-a4a38c66-7b97-4ed4-8de2-e13c6fed8b6f" targetRef="sid-e64c88c2-544d-4040-bda3-a4dfee23bbb0"/>
<userTask id="sid-00eaf072-ae60-4320-b5d7-42ed9e0d5481" name="请假" flowable:assignee="${leaveTask}">
<extensionElements>
<flowable:formProperty id="Property 1"/>
<flowable:formProperty id="Property 2"/>
</extensionElements>
</userTask>
<sequenceFlow id="sid-b416987b-e650-4b93-918a-4fb63a45c3f7" sourceRef="sid-c5a66567-d665-4139-9fbd-392741c61671" targetRef="sid-00eaf072-ae60-4320-b5d7-42ed9e0d5481"/>
<sequenceFlow id="sid-5bc32db7-bd60-4d9c-bdfd-4ec25927940a" sourceRef="sid-00eaf072-ae60-4320-b5d7-42ed9e0d5481" targetRef="sid-eb308e04-f77b-4576-bc43-161deda4abe9"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_ask_for_leave">
<bpmndi:BPMNPlane bpmnElement="ask_for_leave" id="BPMNPlane_ask_for_leave">
<bpmndi:BPMNShape id="shape-24e4d6e8-9447-432b-9a99-6f0bc013ee25" bpmnElement="sid-c5a66567-d665-4139-9fbd-392741c61671">
<omgdc:Bounds x="-333.44" y="-67.50001" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-3d989d45-e338-4c9c-aad8-d6603c837e70" bpmnElement="sid-eb308e04-f77b-4576-bc43-161deda4abe9">
<omgdc:Bounds x="-49.78" y="-92.5" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-4b510886-b43b-40c6-8290-b16faa66f525" bpmnElement="sid-9fe275b2-c910-4449-ba1a-227815280f1d">
<omgdc:Bounds x="111.5" y="-72.5" width="40.0" height="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-bd4885c5-5d7f-4ee3-9920-7d8b2164c0b2" bpmnElement="sid-34745413-a80c-445a-93f3-f476f0ccb787">
<omgdi:waypoint x="50.22" y="-52.5"/>
<omgdi:waypoint x="111.5" y="-52.5"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-329f11e7-b238-4d00-959d-27d4181a70eb" bpmnElement="sid-ffb1f1ad-a139-47ee-9af8-4b19b14faa05">
<omgdc:Bounds x="229.24002" y="-92.5" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-607dcc38-7f2d-4832-9490-3459e5e285a2" bpmnElement="sid-ed534be0-25ad-4b49-8254-142b429079f8">
<omgdi:waypoint x="151.5" y="-52.5"/>
<omgdi:waypoint x="229.24002" y="-52.5"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-1b76b4bd-a978-481e-ae61-2fb34aa93287" bpmnElement="sid-e64c88c2-544d-4040-bda3-a4dfee23bbb0">
<omgdc:Bounds x="-73.58002" y="112.399994" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-909732fc-d71f-404c-a532-e0af30c1bde7" bpmnElement="sid-db63e3b6-f3e6-4376-8163-a5132c5428b6">
<omgdi:waypoint x="131.5" y="-32.5"/>
<omgdi:waypoint x="131.5" y="87.4"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-ba425d93-3944-4c6e-936f-46cca11fb5b2" bpmnElement="sid-8a49b07f-c358-4c22-aeea-ab20582159be">
<omgdc:Bounds x="434.34" y="-72.5" width="40.0" height="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-6dcefc26-4e80-46e6-885b-5fe38aac992e" bpmnElement="sid-39e9567a-a55d-40e2-a6a9-d0965f143246">
<omgdi:waypoint x="329.24002" y="-52.5"/>
<omgdi:waypoint x="434.34" y="-52.5"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-084b62a1-3ac1-42cd-813b-a39e67f69a0d" bpmnElement="sid-04fde03b-b8b9-407c-8719-47757478c318">
<omgdc:Bounds x="578.78" y="-67.5" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-c5b3c597-f000-4577-85ea-2aa77cc0f9c2" bpmnElement="sid-85980f9d-128c-41c9-8e85-3b363f5e952d">
<omgdi:waypoint x="474.34" y="-52.5"/>
<omgdi:waypoint x="578.78" y="-52.5"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-62b4ce99-5635-4e2f-bac9-7ad6fe4480eb" bpmnElement="sid-0c361f56-426a-4522-81c8-82bfe0f36578">
<omgdi:waypoint x="454.34" y="-32.5"/>
<omgdi:waypoint x="454.34" y="125.960014"/>
<omgdi:waypoint x="181.5" y="127.40001"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-21815641-f954-4587-b3b3-142a40076ce6" bpmnElement="sid-a4a38c66-7b97-4ed4-8de2-e13c6fed8b6f">
<omgdc:Bounds x="81.49999" y="87.4" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-8a27423c-db35-4cd7-b434-023184af6744" bpmnElement="sid-bd7cd20d-3131-40a4-b4b8-72b5d597f89c">
<omgdi:waypoint x="81.5" y="127.399994"/>
<omgdi:waypoint x="-43.580017" y="127.399994"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-916f5507-23d8-443d-bbd9-2644cdb1ea2d" bpmnElement="sid-00eaf072-ae60-4320-b5d7-42ed9e0d5481">
<omgdc:Bounds x="-219.55603" y="-92.50001" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-2634fcc3-08f2-4215-bcb1-25e3ccf8be6a" bpmnElement="sid-b416987b-e650-4b93-918a-4fb63a45c3f7">
<omgdi:waypoint x="-303.44" y="-52.500008"/>
<omgdi:waypoint x="-219.55603" y="-52.500008"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-dfad2c73-e7d3-45de-b64d-0e31c5793681" bpmnElement="sid-5bc32db7-bd60-4d9c-bdfd-4ec25927940a">
<omgdi:waypoint x="-119.55603" y="-52.500008"/>
<omgdi:waypoint x="-49.78" y="-52.5"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
3、核心依赖
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
4、部署流程
将创建好的bpmn文件进行部署,使用repositoryService
@Component
@Slf4j
public class BpmnDeployment {
@Autowired
private RepositoryService repositoryService;
@PostConstruct
public void deployBpmn() {
try {
// 从类路径加载BPMN文件
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("process/ask_for_leave.bpmn20.xml") // 或者使用 addInputStream 方法添加流
.name("ask for leave Deployment")
.deploy(); // 部署流程定义
log.info("部署ID: " + deployment.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、初始化
基于springboot启动,定义好数据源,会自动创建数据表
6、使用
主要包括请假流程,待办,已办,全部流程查询等
@Service
@Slf4j
public class AskForLeaveServiceImpl implements AskForLeaveService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private HistoryService historyService;
/**
* 流程Id
*/
private static final String processId = "ask_for_leave:4:463739af-bad5-11f0-ad2d-123d1c304878";
/**
* 员工
*/
private static final String staffId = "1";
/**
* 组长
*/
private static final String zuzhangId = "2";
/**
* 经理
*/
private static final String managerId = "3";
/**
* 发起请假流程
*/
@Override
public void startProcess() {
Map<String, Object> variables = new HashMap<>();
variables.put("leaveTask", staffId);
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processId, variables);
runtimeService.setVariable(processInstance.getId(), "name", "staffOne");
runtimeService.setVariable(processInstance.getId(), "reason", "rest");
runtimeService.setVariable(processInstance.getId(), "days", "5");
log.info("leave task start ,processId={}", processInstance.getProcessInstanceId());
List<Task> list = taskService.createTaskQuery().taskAssignee(staffId).orderByTaskId().desc().list();
for (Task task : list) {
Map<String, Object> map = new HashMap<>();
//提交给组长的时候,需要指定组长的 id
map.put("zuzhangTask", zuzhangId);
log.info("startProcess taskId={}", task.getId());
taskService.complete(task.getId(), map);
}
}
/**
* 组长审批通过
*/
@Override
public void zuzhangCheckPass() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
log.info("zuzhangCheckPass taskId={}", task.getId());
//获取流程变量
Map<String, Object> variables = runtimeService.getVariables(task.getProcessInstanceId());
log.info("查看流程变量,{}", JSON.toJSONString(variables));
Map<String, Object> map = new HashMap<>();
map.put("managerTask", managerId);
map.put("checkResult", "pass");
taskService.complete(task.getId(), map);
}
}
/**
* 组长审批拒绝
*/
@Override
public void zuzhangCheckReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
log.info("流程名称={}", processInstance.getName());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "reject");
log.info("zuzhangCheckReject taskId={}", task.getId());
taskService.complete(task.getId(), map);
}
}
/**
* 经理审批通过
*/
@Override
public void manageCheckPass() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "pass");
log.info("manageCheckPass taskId={}", task.getId());
taskService.complete(task.getId(), map);
}
}
/**
* 经理审批拒绝
*/
@Override
public void manageCheckReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "reject");
log.info("manageCheckReject taskId={}", task.getId());
taskService.complete(task.getId(), map);
}
}
/**
* 显示所有流程
*/
@Override
public void showAllProcess() {
List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery().latestVersion().list();
for (ProcessDefinition processDefinition : processDefinitions) {
log.info("流程定义ID: " + processDefinition.getId());
log.info("流程定义键: " + processDefinition.getKey());
log.info("流程定义名称: " + processDefinition.getName());
log.info("流程定义版本: " + processDefinition.getVersion());
log.info("流程定义部署ID: " + processDefinition.getDeploymentId());
log.info("-----");
}
}
/**
* 待办流程
*/
@Override
public void waitingProcess() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
log.info("taskId={}", task.getId());
log.info("taskName={}", task.getName());
log.info("processInstanceId={}", task.getProcessInstanceId());
log.info("processDefinitionId={}", task.getProcessDefinitionId());
}
}
/**
* 已办流程
*/
@Override
public void endProcess() {
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
.taskAssignee(staffId)
.finished()
.orderByHistoricTaskInstanceEndTime().desc()
.list();
for (HistoricTaskInstance task : list) {
log.info("taskId={}", task.getId());
log.info("taskName={}", task.getName());
log.info("processInstanceId={}", task.getProcessInstanceId());
log.info("processDefinitionId={}", task.getProcessDefinitionId());
}
}
}
7、serviceTask
bpmn文件中serviceTask指定flowable:class
@Component("myServiceTask")
@Slf4j
public class EmailServiceTask implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
//可以自定义一系列操作
String processInstanceId = execution.getProcessInstanceId();
String processDefinitionId = execution.getProcessDefinitionId();
String currentActivityId = execution.getCurrentActivityId();
Map<String, Object> variables = execution.getVariables();
log.info("email service task start");
log.info("processInstanceId={},processDefinitionId={},currentActivityId={}", processInstanceId, processDefinitionId, currentActivityId);
}
}
8、展示流程图
中文字体配置,防止乱码
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
engineConfiguration.setActivityFontName("宋体");
engineConfiguration.setLabelFontName("宋体");
engineConfiguration.setAnnotationFontName("宋体");
}
}
@RestController
public class HelloController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private ProcessEngine processEngine;
@Autowired
private HistoryService historyService;
/**
* 显示正在进行中的流程图
* @param resp
* @param processId
* @throws Exception
*/
@GetMapping("/pic")
public void showPic(HttpServletResponse resp, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
if (pi == null) {
return;
}
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(processId)
.list();
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, false);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = resp.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
/**
* 显示已结束的流程图
* @param processId
* @param response
* @throws IOException
*/
@GetMapping("/picHistory")
public void showImages(String processId, HttpServletResponse response) throws IOException {
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().processInstanceId(processId).finished().list();
HistoricProcessInstance historicProcessInstance = list.get(0);
String processDefinitionId = historicProcessInstance.getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
List<HistoricActivityInstance> historyProcess = historyService // 历史相关Service
.createHistoricActivityInstanceQuery() // 创建历史活动实例查询
.processInstanceId(processId) // 执行流程实例id
.finished()
.list();
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
//获取流程图
for (HistoricActivityInstance hi : historyProcess) {
String activityType = hi.getActivityType();
if (activityType.equals("sequenceFlow") || activityType.equals("exclusiveGateway")) {
flows.add(hi.getActivityId());
} else if (activityType.equals("userTask") || activityType.equals("startEvent")) {
activityIds.add(hi.getActivityId());
}
}
ProcessEngineConfiguration engConf = ProcessEngines.getDefaultProcessEngine().getProcessEngineConfiguration();
//定义流程画布生成器
ProcessDiagramGenerator processDiagramGenerator = engConf.getProcessDiagramGenerator();
InputStream in = processDiagramGenerator.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 = response.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
效果如下 正在进行中的流程

历史流程