前言

我们在项目中需要使用到Flowable,鉴于之前对流程引擎并不是很了解,这里准备写一篇作为入门文章。

实践

这次例子是基于springboot + Flowable ,springboot 的版本是2.3.4.RELEASE,Flowable 的版本为6.3.0

1、依赖

<!--flowable工作流依赖-->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>6.3.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
          <!-- 加载jdbc连接数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 加载mybatis jar包 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>compile</scope>
        </dependency>

2、依赖(主要添加数据库和flowable相关依赖)

server:
  port: 8082
spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://localhost:3306/test_mybatis?serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    hikari:
      read-only: false
      #客户端等待连接池连接的最大毫秒数
      connection-timeout: 60000
      #允许连接在连接池中空闲的最长时间(以毫秒为单位)
      idle-timeout: 60000
      #连接将被测试活动的最大时间量
      validation-timeout: 3000
      #池中连接关闭后的最长生命周期
      max-lifetime: 60000
      #最大池大小
      maximum-pool-size: 60
      #连接池中维护的最小空闲连接数
      minimum-idle: 10
      #从池返回的连接的默认自动提交行为。默认值为true
      auto-commit: true
      #如果您的驱动程序支持JDBC4,我们强烈建议您不要设置此属性
      connection-test-query: SELECT 1
      #自定义连接池名称
      pool-name: myHikarCp

flowable:
  database-schema-update: false
    #关闭定时任务JOB
  async-executor-activate: false

3、如何定义xml

flowable建议采用业界标准BPMN2.0的XML来描述需要定义的工作流。所以我们需要在项目中创建一个流程定义,下面这个是我创建的一个xml定义,

spring boot 集成帆软 springboot集成flowable_流程引擎

很多文章上面并没有如何定义BPMN2.0的XML的操作(主要需要 tomcat 和下载flower)
(1) 下载tomcat

[tomcat 下载官网](https://tomcat.apache.org/download-80.cgi)

(2) 下载flowable的zip文件

https://github.com/flowable/flowable-engine/releases/download/flowable-6.4.0/flowable-6.4.0.zip 下载flowable的zip文件

下载下来解压之后,我们把flowable-6.4.0/wars 下的所有war包复制到tomcat/webapps下面:

spring boot 集成帆软 springboot集成flowable_spring boot 集成帆软_02

拷入

spring boot 集成帆软 springboot集成flowable_flowable_03

(3)登录编辑流程图

启动成功之后,可以登陆创建流程:http://localhost:8080/flowable-modeler :

默认账号密码:admin 密码 test

spring boot 集成帆软 springboot集成flowable_流程引擎_04


BPMN2.0 图有两个需要注意的地方,确定Flowable 中的审批,驳回放置内容(这里例子用的${taskUser} ,也可以用其他的,决定放置到那个地方)

spring boot 集成帆软 springboot集成flowable_spring_05

这里判断之后走那个流程需要(${finishFlag==“NO”} 或者 ${finishFlag==“YES”} )选择走那个方向,这里的这两个都是自定义的

spring boot 集成帆软 springboot集成flowable_spring boot 集成帆软_06

3、例子使用BPMN2.0的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="adviceApply" name="投诉建议" isExecutable="true">
    <documentation>投诉建议流程</documentation>
    <startEvent id="startEvent1" name="开始" flowable:formFieldValidation="true"></startEvent>
    <userTask id="customerService" name="客服代表" flowable:candidateGroups="${customerServiceId}" flowable:formFieldValidation="true"></userTask>
    <sequenceFlow id="sid-C443333F-D5FF-41E4-9F84-AA4B33BC57AB" sourceRef="startEvent1" targetRef="customerService"></sequenceFlow>
    <userTask id="department" name="部门领导" flowable:assignee="${taskUser}" flowable:formFieldValidation="true">
      <extensionElements>
        <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
      </extensionElements>
    </userTask>
    <userTask id="support" name="专业主管" flowable:assignee="${taskUser}" flowable:formFieldValidation="true">
      <extensionElements>
        <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
      </extensionElements>
    </userTask>
    <endEvent id="sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5" name="结束"></endEvent>
    <exclusiveGateway id="sid-A93C80FA-86B7-4200-B486-2A8B4719748D"></exclusiveGateway>
    <exclusiveGateway id="sid-70D7E21E-345D-4CC8-BCDF-55F0DC887303"></exclusiveGateway>
    <exclusiveGateway id="sid-4E7CF6E7-796A-48D1-BACA-FE3C1D95F21B"></exclusiveGateway>
    <userTask id="feedback" name="客服代表" flowable:candidateGroups="${customerServiceId}" flowable:formFieldValidation="true"></userTask>
    <sequenceFlow id="f10" sourceRef="feedback" targetRef="sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5"></sequenceFlow>
    <sequenceFlow id="f1" sourceRef="customerService" targetRef="sid-A93C80FA-86B7-4200-B486-2A8B4719748D"></sequenceFlow>
    <sequenceFlow id="f2" sourceRef="sid-A93C80FA-86B7-4200-B486-2A8B4719748D" targetRef="department">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${finishFlag=="NO"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="f4" sourceRef="department" targetRef="sid-70D7E21E-345D-4CC8-BCDF-55F0DC887303"></sequenceFlow>
    <sequenceFlow id="f7" sourceRef="support" targetRef="sid-4E7CF6E7-796A-48D1-BACA-FE3C1D95F21B"></sequenceFlow>
    <sequenceFlow id="f5" sourceRef="sid-70D7E21E-345D-4CC8-BCDF-55F0DC887303" targetRef="support">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${finishFlag=="NO"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="f8" sourceRef="sid-4E7CF6E7-796A-48D1-BACA-FE3C1D95F21B" targetRef="feedback">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${finishFlag=="NO"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="f9" sourceRef="sid-4E7CF6E7-796A-48D1-BACA-FE3C1D95F21B" targetRef="sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${finishFlag=="YES"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="f6" sourceRef="sid-70D7E21E-345D-4CC8-BCDF-55F0DC887303" targetRef="sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${finishFlag=="YES"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="f3" name="通过" sourceRef="sid-A93C80FA-86B7-4200-B486-2A8B4719748D" targetRef="sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${finishFlag=="YES"}]]></conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_adviceApply">
    <bpmndi:BPMNPlane bpmnElement="adviceApply" id="BPMNPlane_adviceApply">
      <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
        <omgdc:Bounds height="30.0" width="30.0" x="40.0" y="163.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="customerService" id="BPMNShape_customerService">
        <omgdc:Bounds height="80.0" width="100.0" x="115.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="department" id="BPMNShape_department">
        <omgdc:Bounds height="80.0" width="100.0" x="350.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="support" id="BPMNShape_support">
        <omgdc:Bounds height="80.0" width="100.0" x="585.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5" id="BPMNShape_sid-D7B9B979-C32C-4959-A4F7-129C0691D8E5">
        <omgdc:Bounds height="28.0" width="28.0" x="866.0" y="300.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-A93C80FA-86B7-4200-B486-2A8B4719748D" id="BPMNShape_sid-A93C80FA-86B7-4200-B486-2A8B4719748D">
        <omgdc:Bounds height="40.0" width="40.0" x="270.9999791604486" y="158.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-70D7E21E-345D-4CC8-BCDF-55F0DC887303" id="BPMNShape_sid-70D7E21E-345D-4CC8-BCDF-55F0DC887303">
        <omgdc:Bounds height="40.0" width="40.0" x="500.0" y="158.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-4E7CF6E7-796A-48D1-BACA-FE3C1D95F21B" id="BPMNShape_sid-4E7CF6E7-796A-48D1-BACA-FE3C1D95F21B">
        <omgdc:Bounds height="40.0" width="40.0" x="740.0" y="158.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="feedback" id="BPMNShape_feedback">
        <omgdc:Bounds height="80.0" width="100.0" x="830.0" y="138.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="f6" id="BPMNEdge_f6">
        <omgdi:waypoint x="520.5" y="197.44301253687323"></omgdi:waypoint>
        <omgdi:waypoint x="520.5" y="314.0"></omgdi:waypoint>
        <omgdi:waypoint x="866.0" y="314.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f7" id="BPMNEdge_f7">
        <omgdi:waypoint x="684.9499999999999" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="740.0" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f8" id="BPMNEdge_f8">
        <omgdi:waypoint x="779.5215994962216" y="178.42016806722688"></omgdi:waypoint>
        <omgdi:waypoint x="829.9999999999989" y="178.20899581589958"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f9" id="BPMNEdge_f9">
        <omgdi:waypoint x="760.5" y="197.44301253687317"></omgdi:waypoint>
        <omgdi:waypoint x="760.5" y="314.0"></omgdi:waypoint>
        <omgdi:waypoint x="866.0" y="314.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f10" id="BPMNEdge_f10">
        <omgdi:waypoint x="880.0" y="217.95000000000002"></omgdi:waypoint>
        <omgdi:waypoint x="880.0" y="300.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-C443333F-D5FF-41E4-9F84-AA4B33BC57AB" id="BPMNEdge_sid-C443333F-D5FF-41E4-9F84-AA4B33BC57AB">
        <omgdi:waypoint x="69.94999848995758" y="178.0"></omgdi:waypoint>
        <omgdi:waypoint x="114.9999999999917" y="178.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f1" id="BPMNEdge_f1">
        <omgdi:waypoint x="214.94999999999868" y="178.1974308625642"></omgdi:waypoint>
        <omgdi:waypoint x="271.4206140679542" y="178.42063490750846"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f2" id="BPMNEdge_f2">
        <omgdi:waypoint x="310.528864436638" y="178.41203705401028"></omgdi:waypoint>
        <omgdi:waypoint x="349.99999999999466" y="178.23018428758581"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f3" id="BPMNEdge_f3">
        <omgdi:waypoint x="291.0" y="197.94783897384158"></omgdi:waypoint>
        <omgdi:waypoint x="291.0" y="314.1342468261719"></omgdi:waypoint>
        <omgdi:waypoint x="866.0" y="314.1342468261719"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f4" id="BPMNEdge_f4">
        <omgdi:waypoint x="449.9499999999953" y="178.20726141078836"></omgdi:waypoint>
        <omgdi:waypoint x="500.4166666666667" y="178.41666666666669"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="f5" id="BPMNEdge_f5">
        <omgdi:waypoint x="539.5247370727428" y="178.41666666666663"></omgdi:waypoint>
        <omgdi:waypoint x="584.9999999999953" y="178.21812227074233"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

4、添加关于FlowableConfig 的配置文件,防止乱码

@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
 
 
    @Override
    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
        engineConfiguration.setActivityFontName("宋体");
        engineConfiguration.setLabelFontName("宋体");
        engineConfiguration.setAnnotationFontName("宋体");
    }
}

5、启动的时候需要动态部署流程实例
这里交给CommandLineRunner 方式来实现

@SpringBootApplication
public class MybatisPlusApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }

    @Resource
    private RepositoryService repositoryService;

    @Override
    public void run(String... args)  {
        InputStream inputStream = this.getClass().getResourceAsStream("/process/投诉建议.bpmn20.xml");
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
        deploymentBuilder.addInputStream("投诉建议.bpmn20.xml", inputStream);
        deploymentBuilder.deploy();
    }
}

6、controller测试类

package com.yin.demo.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.identitylink.api.history.HistoricIdentityLink;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
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;

@Slf4j
@RestController
@RequestMapping("/flower")
@Api(tags = {"流程引擎-API"})
public class FlowerController {

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private TaskService taskService;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private ProcessEngine processEngine;


    /**
     * 启动流程()
     * @param roleId    角色id(我这里配置候选用户,这里就是用roleId)
     */
    @PostMapping(value = "add")
    @ApiOperation("创建流程引擎")
    public String addExpense(String roleId) {
        //启动流程
        HashMap<String, Object> map = new HashMap<>(4);

        //name="客服代表" flowable:candidateGroups="${customerServiceId}"

        map.put("customerServiceId", roleId);

        //<process id="adviceApply" name="投诉建议" isExecutable="true">
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("adviceApply", map);
        return "提交成功.流程Id为:" + processInstance.getId();
    }

    /**
     * 流转
     *
     * @param processInstanceId 流程id
     */
    @PutMapping(value = "apply")
    @ApiOperation("流传流程")
    public String apply(String processInstanceId,Long userId,boolean isPass) {
       // Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        //查询当前办理人的任务ID
        Task task = taskService.createTaskQuery()
                //使用流程实例ID
                .processInstanceId(processInstanceId)
                //任务办理人
                .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!";
    }

    /**
     * 生成流程图
     *
     * @param processId 任务ID
     */
    @GetMapping(value = "processDiagram")
    @ApiOperation("生成流程图")
    public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();

        //流程走完的不显示图
        if (pi == null) {
            return;
        }
        Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
        String InstanceId = task.getProcessInstanceId();
        List<Execution> executions = runtimeService
                .createExecutionQuery()
                .processInstanceId(InstanceId)
                .list();

        //得到正在执行的Activity的Id
        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);
        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);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }

}

演示

创建一个流程

spring boot 集成帆软 springboot集成flowable_流程引擎_07

查看当前流程

spring boot 集成帆软 springboot集成flowable_spring boot 集成帆软_08

进行流程流转

spring boot 集成帆软 springboot集成flowable_spring boot 集成帆软_09

再次查看当前流程

spring boot 集成帆软 springboot集成flowable_流程引擎_10

附录

这里面的演示都使用很多很多service,这里简单讲解一下如何做出信息

spring boot 集成帆软 springboot集成flowable_流程引擎_11