flowable工作流

1、介绍

Flowable 是一个轻量级的业务流程引擎,基于 BPMN 2.0 规范实现。它可以帮助开发者将业务流程可视化,并提供了强大的流程控制和管理功能。Flowable 适用于各种业务场景,如审批流程、工单处理、订单履行等。

2、BPMN文件创建

IDEA中安装插件Flowable BPMN visualizer,通过该插件可以快速创建bpmn文件。

以请假流程为例

image-20251106161722937

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


}

效果如下 正在进行中的流程

image-20251106163350386

历史流程 image-20251106163756839

results matching ""

    No results matching ""