我们在开发自动化办公软件时经常会遇到各种审批流程功能,这个使用就需要使用到工作流引擎。目前主流的工作流引擎有Activiti、Flowable、camunda,其中Flowable是在Activiti的基础上开发出来的,基于BPMN2.0协议,它包括 BPMN(Business Process Model and Notation)引擎、CMMN(Case Management Model and Notation)引擎、DMN(Decision Model and Notation)引擎、表单引擎(Form Engine)等模块。
这里我们基于springboot和PostgreSQL数据库,介绍一下如何搭建一个基础的Flowable工作流框架。
创建项目,添加依赖
- 下图是我在线创建的springboot项目,只添加了Spring Web和Postgre SQL Driver两个依赖。

 - 添加Flowable依赖
 
<dependency>
	<groupId>org.flowable</groupId>
	<artifactId>flowable-spring-boot-starter</artifactId>
	<version>6.7.2</version>
</dependency>
 
-  
配置数据库链接
这里要解释一下,网上有的博文说,添加完数据库,启动服务,就会在数据库里自动创建一堆表,本人测试需要创建bpmn20.xml文件后,数据库才会自动生成对应的表。
 
spring:
  datasource:
    username: postgres
    password: postgres
    url: jdbc:postgresql://192.168.2.172/test
 
到这里已经完成了项目的基础配置。
bpmn20.xml文件制作
在整个工作流框架中,比较核心的是bpmn20.xml文件的制作,如果之前没有一定的基础,制作整个文件还是有点难度的。idea的插件市场里有一个Flowable BPMN visualizer插件,可以在idea中直接用来制作bpmn20.xml文件。
 
 安装插件后,在resources文件夹下创建processes文件夹,流程文件默认放在此文件夹下。idea中右键创建文件,选择BPMN 2.0 file。
 
 注意文件名要以**.bpmn20.xml**结尾。在创建的文件上,右键选择View BPMN。
 
 在弹出的编辑器中,即可进行相关的绘制操作。新手,注意,在编辑器上右键可以创建节点。
 
 网上也有一些其他工具可以用来绘制的,都可以尝试。我的xml文件源码如下:
<?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-8f75290b-6d4f-4304-98e3-592be1f19a4e"/>
    <endEvent id="sid-4d3f3115-90f9-4cad-a8d3-df415f8aaa9d"/>
    <userTask id="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a" name="请假"/>
    <userTask id="sid-7b7c9585-5913-49fe-910f-fea79e53518c" name="组长审批" flowable:assignee="${taskUser}"/>
    <userTask id="sid-627ef23c-3f54-4536-bda9-bbf0d3243230" name="经理审批" flowable:assignee="${taskUser}"/>
    <exclusiveGateway id="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f" />
    <sequenceFlow id="sid-b1bdf216-3433-4052-ae80-ad753e16aed7" sourceRef="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f" targetRef="sid-627ef23c-3f54-4536-bda9-bbf0d3243230" name="通过">
      <conditionExpression xsi:type="tFormalExpression">${finishFlag==\"YES\"}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-0b327947-1e7f-48c3-8047-e8afc3d15005" sourceRef="sid-8f75290b-6d4f-4304-98e3-592be1f19a4e" targetRef="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a"/>
    <sequenceFlow id="sid-e5703760-35e2-497f-9501-7b616d5c1d02" sourceRef="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a" targetRef="sid-7b7c9585-5913-49fe-910f-fea79e53518c"/>
    <sequenceFlow id="sid-17b98be9-ef29-48a8-8cf6-cfde4be9d7c9" sourceRef="sid-7b7c9585-5913-49fe-910f-fea79e53518c" targetRef="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f"/>
    <serviceTask id="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" flowable:exclusive="true" name="发送失败消息" flowable:class="com.work.flow.listener.TestServiceListener"/>
    <sequenceFlow id="sid-7f8d7cc2-ddd0-4cb4-9ce6-81e8b42f4717" sourceRef="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f" targetRef="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" name="不通过">
      <conditionExpression xsi:type="tFormalExpression">${finishFlag==\"NO\"}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-95ed6745-c15f-4489-a9a5-93f4aeea1f43" sourceRef="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" targetRef="sid-4d3f3115-90f9-4cad-a8d3-df415f8aaa9d"/>
    <exclusiveGateway id="sid-03e9886b-d80c-4a5a-8916-76f2b6683727" />
    <sequenceFlow id="sid-e8f7bf08-9b5b-4f0b-b0aa-c1a74d42e870" sourceRef="sid-03e9886b-d80c-4a5a-8916-76f2b6683727" targetRef="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" name="不通过">
      <conditionExpression xsi:type="tFormalExpression">${finishFlag==\"NO\"}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-fc8f3bf6-d817-4d6b-9dbd-f15d11032fae" sourceRef="sid-627ef23c-3f54-4536-bda9-bbf0d3243230" targetRef="sid-03e9886b-d80c-4a5a-8916-76f2b6683727"/>
    <endEvent id="sid-f7bc1956-c2b1-4036-99fa-0318f6da4221"/>
    <sequenceFlow id="sid-2a97e964-4e84-4936-9356-14b7b7b6ecb4" sourceRef="sid-03e9886b-d80c-4a5a-8916-76f2b6683727" targetRef="sid-f7bc1956-c2b1-4036-99fa-0318f6da4221" name="通过">
      <conditionExpression xsi:type="tFormalExpression">${finishFlag==\"YES\"}</conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_ask_for_leave">
    <bpmndi:BPMNPlane bpmnElement="ask_for_leave" id="BPMNPlane_ask_for_leave">
      <bpmndi:BPMNShape id="shape-44c54737-7c91-4844-8210-7db7f6853bb6" bpmnElement="sid-8f75290b-6d4f-4304-98e3-592be1f19a4e">
        <omgdc:Bounds x="-245.0" y="-65.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-12f82f19-3220-442d-893b-b139db4834e4" bpmnElement="sid-4d3f3115-90f9-4cad-a8d3-df415f8aaa9d">
        <omgdc:Bounds x="-155.0" y="15.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-146cee26-7a5b-4da7-9bbb-6c392e645b94" bpmnElement="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a">
        <omgdc:Bounds x="-180.0" y="-75.0" width="55.0" height="50.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-bd5bc59b-ab57-4e3c-8dfa-cb2827d9a331" bpmnElement="sid-7b7c9585-5913-49fe-910f-fea79e53518c">
        <omgdc:Bounds x="-75.0" y="-80.0" width="80.0" height="60.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-cc2b2bce-04fd-4641-ae8b-419d9f79c2b0" bpmnElement="sid-627ef23c-3f54-4536-bda9-bbf0d3243230">
        <omgdc:Bounds x="185.0" y="-97.5" width="100.0" height="55.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-43f90d29-b7d6-4a74-8acb-1fc7f3f29289" bpmnElement="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f">
        <omgdc:Bounds x="55.0" y="-80.0" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-9eca388f-9baf-4a0b-8201-892c738b6ad0" bpmnElement="sid-b1bdf216-3433-4052-ae80-ad753e16aed7">
        <omgdi:waypoint x="95.0" y="-60.0"/>
        <omgdi:waypoint x="185.0" y="-70.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-a5e04944-60fb-4696-ad16-c8092d6c0bb5" bpmnElement="sid-0b327947-1e7f-48c3-8047-e8afc3d15005">
        <omgdi:waypoint x="-215.0" y="-50.0"/>
        <omgdi:waypoint x="-180.0" y="-50.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-6fb6a4c1-3b9a-4238-a66b-10f3d8571af1" bpmnElement="sid-e5703760-35e2-497f-9501-7b616d5c1d02">
        <omgdi:waypoint x="-125.0" y="-50.0"/>
        <omgdi:waypoint x="-75.0" y="-50.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-e23fcf4e-1309-4639-a40e-9f0fe3fb42b7" bpmnElement="sid-17b98be9-ef29-48a8-8cf6-cfde4be9d7c9">
        <omgdi:waypoint x="5.0" y="-50.0"/>
        <omgdi:waypoint x="55.0" y="-60.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-d6c43ede-95db-49fe-a468-0c8678deff6f" bpmnElement="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a">
        <omgdc:Bounds x="-20.0" y="7.5" width="110.0" height="75.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-80b91b72-184a-4add-878d-a0553d8fdd04" bpmnElement="sid-7f8d7cc2-ddd0-4cb4-9ce6-81e8b42f4717">
        <omgdi:waypoint x="75.0" y="-40.0"/>
        <omgdi:waypoint x="62.5" y="7.5"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-92accc38-c426-45f8-bf82-4ab1d8b6625b" bpmnElement="sid-95ed6745-c15f-4489-a9a5-93f4aeea1f43">
        <omgdi:waypoint x="-20.0" y="45.0"/>
        <omgdi:waypoint x="-125.0" y="30.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-aed10d1f-11b7-4c70-91ca-63514182f93f" bpmnElement="sid-03e9886b-d80c-4a5a-8916-76f2b6683727">
        <omgdc:Bounds x="225.0" y="15.0" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-1a9651dc-930e-4642-b2f3-4ba0b47fe97d" bpmnElement="sid-e8f7bf08-9b5b-4f0b-b0aa-c1a74d42e870">
        <omgdi:waypoint x="225.0" y="35.0"/>
        <omgdi:waypoint x="90.0" y="45.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-5e55bb58-f07a-45d5-bb8a-8c13a6c0b9dd" bpmnElement="sid-fc8f3bf6-d817-4d6b-9dbd-f15d11032fae">
        <omgdi:waypoint x="260.0" y="-42.5"/>
        <omgdi:waypoint x="245.0" y="15.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-12be209b-f581-4e87-b480-41c60acb8d8e" bpmnElement="sid-f7bc1956-c2b1-4036-99fa-0318f6da4221">
        <omgdc:Bounds x="255.0" y="100.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-7aba8c58-63e5-4c57-b3d7-679ad58b6388" bpmnElement="sid-2a97e964-4e84-4936-9356-14b7b7b6ecb4">
        <omgdi:waypoint x="245.0" y="55.0"/>
        <omgdi:waypoint x="270.0" y="100.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
 
接口开发
-  
防止流程图乱码配置
package com.work.flow.config; import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; import org.springframework.context.annotation.Configuration; @Configuration public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> { @Override public void configure(SpringProcessEngineConfiguration engineConfiguration) { engineConfiguration.setActivityFontName("宋体"); engineConfiguration.setLabelFontName("宋体"); engineConfiguration.setAnnotationFontName("宋体"); } } -  
发起流程接口
package com.work.flow.controller; import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.*; 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.PostMapping; import org.springframework.web.bind.annotation.RequestParam; 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; @RestController public class TestController { @Autowired RuntimeService runtimeService; @Autowired TaskService taskService; @Autowired RepositoryService repositoryService; @Autowired ProcessEngine processEngine; @GetMapping( "/add") public String addExpense(@RequestParam String userId) { //启动流程 HashMap<String, Object> map = new HashMap<>(4); //name="客服代表" flowable:candidateGroups="${customerServiceId}" map.put("userId", userId); //<process id="adviceApply" name="投诉建议" isExecutable="true"> ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ask_for_leave", map); return "提交成功.流程Id为:" + processInstance.getId(); } }
 -  
获取流程图接口
 
@GetMapping("/pic")
    public void showPic(HttpServletResponse resp, @RequestParam String processId) throws Exception {
        System.out.println("收到请求"+processId);
        List<ProcessInstance> instances  =runtimeService.createProcessInstanceQuery().list();
        for (ProcessInstance instance : instances) {
            System.out.println(instance.getId());
        }
        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);
            }
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
            System.out.println("运行结束");
        }
    }
 

- 流程推进接口
 
/**
     * 流转
     *
     * @param processId 流程id
     */
    @GetMapping("/apply")
    public String apply(@RequestParam String processId,@RequestParam String userId,@RequestParam boolean isPass) {
        //查询当前办理人的任务ID
        Task task = taskService.createTaskQuery()
                //使用流程实例ID
                .processInstanceId(processId)
                //任务办理人
                .singleResult();
        if (task == null) {
            throw new RuntimeException("流程不存在");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("taskUser", userId);
        String pass = "NO";
        if(isPass){
            pass = "YES";
        }
        map.put("finishFlag", pass);
        taskService.complete(task.getId(), map);
        return "processed ok!";
    }
 

 走流程后,再次查看流程图
 
总结
- 总体bpmn20.xml的配置比较麻烦,需要一定的专业知识
 - 上手还是有一定的难度的
 


















