springboot集成flowable工作流之梅开三度,常言道温故而知新,咱也回故一下。

1、使用flowable-ui制作流程图

运行flowable-6.6.0官方demo,打开网址:http://localhost:8080/flowable-ui,输入账号admin/test登录即可,如下

spring boot 工作流框架 springboot flowable工作流_flowable


进入APP.MODELER.TITLE创建流程,之后可以导出流程到项目中直接使用。

流程图如下

spring boot 工作流框架 springboot flowable工作流_spring boot_02


导出的工作流文件如下

<?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);
        }
    }

测试

学生发起请假

spring boot 工作流框架 springboot flowable工作流_flowable_03


查看用户的任务

spring boot 工作流框架 springboot flowable工作流_xml_04


查看当前状态的流程图

spring boot 工作流框架 springboot flowable工作流_spring_05


其中红框表示最新的已经执行完成的节点。


spring boot 工作流框架 springboot flowable工作流_xml_06


其中红框表示所有的已经执行完成的节点。

教师审批同意

spring boot 工作流框架 springboot flowable工作流_spring_07


查看流程图

spring boot 工作流框架 springboot flowable工作流_spring_08


spring boot 工作流框架 springboot flowable工作流_spring boot_09


校长审批同意

spring boot 工作流框架 springboot flowable工作流_spring_10


查看流程图

spring boot 工作流框架 springboot flowable工作流_xml_11


spring boot 工作流框架 springboot flowable工作流_spring boot 工作流框架_12


结束审批同意

spring boot 工作流框架 springboot flowable工作流_flowable_13


查看流程图

spring boot 工作流框架 springboot flowable工作流_spring boot_14


spring boot 工作流框架 springboot flowable工作流_spring boot 工作流框架_15