springboot集成flowable工作流之梅开三度,常言道温故而知新,咱也回故一下。
1、使用flowable-ui制作流程图
运行flowable-6.6.0官方demo,打开网址:http://localhost:8080/flowable-ui,输入账号admin/test登录即可,如下
进入APP.MODELER.TITLE创建流程,之后可以导出流程到项目中直接使用。
流程图如下
导出的工作流文件如下
<?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="leave4" name="请假审批" isExecutable="true">
<startEvent id="start" name="开始" flowable:formFieldValidation="true"></startEvent>
<userTask id="student" name="学生" flowable:assignee="${studentId}" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<userTask id="teacher" name="老师" flowable:assignee="${teacherId}" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="gate1"></exclusiveGateway>
<userTask id="header" name="校长" flowable:assignee="${headerId}" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="gate2"></exclusiveGateway>
<endEvent id="end" name="结束"></endEvent>
<sequenceFlow id="flow1" sourceRef="start" targetRef="student"></sequenceFlow>
<sequenceFlow id="flow2" name="请假" sourceRef="student" targetRef="teacher"></sequenceFlow>
<sequenceFlow id="flow3" name="审批" sourceRef="teacher" targetRef="gate1"></sequenceFlow>
<sequenceFlow id="flow4" name="拒绝" sourceRef="gate1" targetRef="student">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${command=='refuse'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" name="同意" sourceRef="gate1" targetRef="header">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${command=='agree'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" name="审批" sourceRef="header" targetRef="gate2"></sequenceFlow>
<sequenceFlow id="flow7" name="同意" sourceRef="gate2" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${command=='agree'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow8" name="拒绝" sourceRef="gate2" targetRef="student">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${command=='refuse'}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave4">
<bpmndi:BPMNPlane bpmnElement="leave4" id="BPMNPlane_leave4">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="student" id="BPMNShape_student">
<omgdc:Bounds height="80.0" width="100.0" x="165.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="teacher" id="BPMNShape_teacher">
<omgdc:Bounds height="80.0" width="100.0" x="320.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="gate1" id="BPMNShape_gate1">
<omgdc:Bounds height="40.0" width="40.0" x="465.0" y="158.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="header" id="BPMNShape_header">
<omgdc:Bounds height="80.0" width="100.0" x="550.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="gate2" id="BPMNShape_gate2">
<omgdc:Bounds height="40.0" width="40.0" x="695.0" y="158.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="780.0" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="129.94999817301806" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="164.999999999925" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="264.9499999998879" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="319.9999999999684" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="419.94999999999806" y="178.21623376623378"></omgdi:waypoint>
<omgdi:waypoint x="465.4130434782609" y="178.4130434782609"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="485.5" y="197.4379452926209"></omgdi:waypoint>
<omgdi:waypoint x="485.5" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="215.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="215.0" y="217.95000000000002"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="504.5247370727428" y="178.41666666666663"></omgdi:waypoint>
<omgdi:waypoint x="549.9999999999953" y="178.21812227074236"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="649.9499999999977" y="178.21623376623376"></omgdi:waypoint>
<omgdi:waypoint x="695.4130434782554" y="178.41304347826085"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="734.5591869398207" y="178.3782051282051"></omgdi:waypoint>
<omgdi:waypoint x="780.0002755524838" y="178.08885188426407"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
<omgdi:waypoint x="715.5" y="158.5"></omgdi:waypoint>
<omgdi:waypoint x="715.5" y="98.0"></omgdi:waypoint>
<omgdi:waypoint x="215.0" y="98.0"></omgdi:waypoint>
<omgdi:waypoint x="215.0" y="138.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
其中学生、教师、校长节点需指定固定值,如${studentId},后面在流程操作中输入指定的id即可。
2、创建springboot工程
pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
application.yml文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable?useSSL=false&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username:
password:
接口
@Autowired
private RuntimeService runtimeService;
@Autowired
private HistoryService historyService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Resource
private ProcessEngine processEngine;
/**
* @description: 发起请假流程(请假)
* @author: ldc
* @date: 2022/1/24 15:13
*/
@RequestMapping(value = "startLeave4")
public String startLeave4(String userId) {
Map<String, Object> map = new HashMap<>();
map.put("studentId", userId);
map.put("leaveDays",2);
map.put("leaveReason","生病");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave4", map);
return "请假流程processInstanceId:" + processInstance.getId();
}
/**
* @description: 获取用户的任务(可以是学生、教师、校长)
* @author: ldc
* @date: 2022/1/24 15:27
*/
@RequestMapping(value = "getTasks4")
public String getTasks4(String userId) {
StringBuilder sb = new StringBuilder();
List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
for (Task task : tasks) {
sb.append("任务taskId: " + task.getId() + ";");
}
return sb.toString();
}
/**
* @description: 审批(同意)
* @author: ldc
* @date: 2022/1/24 15:52
*/
@RequestMapping(value = "agree4")
public String agree4(String user,String userId,String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
System.out.println("该任务对应的流程不存在");
return "该任务对应的流程不存在";
}
Map<String, Object> map = new HashMap<>();
map.put(user, userId);
map.put("command", "agree");
taskService.complete(taskId, map);
return "审核通过";
}
/**
* @description: 审批(拒绝)
* @author: ldc
* @date: 2022/1/24 15:53
*/
@RequestMapping(value = "refuse4")
public String refuse4(String user,String userId,String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
System.out.println("该任务对应的流程不存在");
return "该任务对应的流程不存在";
}
Map<String, Object> map = new HashMap<>();
map.put(user, userId);
map.put("command", "refuse");
taskService.complete(taskId, map);
return "审核驳回";
}
/**
* @description: 查看流程图,待执行的标红
* @author: ldc
* @date: 2022/1/24 17:11
*/
@RequestMapping(value = "processDiagram4")
public void genProcessDiagram4(HttpServletResponse httpServletResponse, String processInstanceId) throws Exception {
// 根据流程实例Id查询该流程实例的具体信息
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
// 获得流程定义Id
String processDefinitionId;
// 流程走完的从历史中获取
if (processInstance == null) {
//return;
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = historicProcessInstance.getProcessDefinitionId();
} else {
processDefinitionId = processInstance.getProcessDefinitionId();
}
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (task == null) {
//return;
}
// 根据流程实例Id查询该流程实例正在执行的活动
List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list();
// 得到正在执行的活动Id
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution execution : executions) {
List<String> ids = runtimeService.getActiveActivityIds(execution.getId());
activityIds.addAll(ids);
}
// 获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
ProcessEngineConfiguration engineConfiguration = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engineConfiguration.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel,
"png",
activityIds,
flows,
engineConfiguration.getActivityFontName(),
engineConfiguration.getLabelFontName(),
engineConfiguration.getAnnotationFontName(),
engineConfiguration.getClassLoader(),
1.0,
true);
OutputStream out = null;
byte[] buf = new byte[1024];
int length = 0;
try {
out = httpServletResponse.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
/**
* @description: 查看流程图,完成的标红
* @author: ldc
* @date: 2022/1/26 11:07
*/
@RequestMapping(value = "processDiagram44")
public void genProcessDiagram44(HttpServletResponse httpServletResponse, String processInstanceId) {
// 获得流程定义Id
String processDefinitionId;
if (this.isFinished(processInstanceId)) {
// 如果流程已经结束
// 根据流程实例Id查询该流程实例的具体信息
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
String id = historicProcessInstance.getId();
processDefinitionId = historicProcessInstance.getProcessDefinitionId();
String startActivityId = historicProcessInstance.getStartActivityId();
String endActivityId = historicProcessInstance.getEndActivityId();
System.out.println("id:" + id + ",processDefinitionId:" + processDefinitionId + ",startActivityId:" + startActivityId + ",endActivityId:" + endActivityId);
} else {
// 如果流程没有结束
// 根据流程实例Id查询该流程实例的具体信息
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = processInstance.getProcessDefinitionId();
String id = processInstance.getId();
String activityId = processInstance.getActivityId();
System.out.println("id:" + id + ",processDefinitionId:" + processDefinitionId + ",activityId:" + activityId);
}
List<String> highLightedActivityIds = new ArrayList<>();
List<String> highLightedFlows = new ArrayList<>();
// 根据流程实例Id查询该流程实例下所有历史活动实例或查询正在执行的活动实例
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
for(HistoricActivityInstance historicActivityInstance : historicActivityInstanceList){
String activityId = historicActivityInstance.getActivityId();
highLightedActivityIds.add(activityId);
if ("startEvent".equals(historicActivityInstance.getActivityType())) {
System.out.println("startEvent" + historicActivityInstance.getActivityName());
//highLightedActivityIds.add(activityId);
}
if ("userTask".equals(historicActivityInstance.getActivityType())) {
System.out.println("userTask" + historicActivityInstance.getActivityName());
//highLightedActivityIds.add(activityId);
}
if ("sequenceFlow".equals(historicActivityInstance.getActivityType())) {
System.out.println("sequenceFlow" + historicActivityInstance.getActivityName());
highLightedFlows.add(historicActivityInstance.getActivityId());
}
if ("endEvent".equals(historicActivityInstance.getActivityType())) {
System.out.println("endEvent" + historicActivityInstance.getActivityName());
//highLightedActivityIds.add(activityId);
}
}
// 根据流程定义Id获取bpmn模型
// 获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
ProcessEngineConfiguration engineConfiguration = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engineConfiguration.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel,
"png",
highLightedActivityIds,
highLightedFlows,
engineConfiguration.getActivityFontName(),
engineConfiguration.getLabelFontName(),
engineConfiguration.getAnnotationFontName(),
engineConfiguration.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);
}
} catch (IOException e) {
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
}
测试
学生发起请假
查看用户的任务
查看当前状态的流程图
其中红框表示最新的已经执行完成的节点。
或
其中红框表示所有的已经执行完成的节点。
教师审批同意
查看流程图
校长审批同意
查看流程图
结束审批同意
查看流程图