Flowable学习(一)——spring boot 部署

这里写目录标题

一、项目搭建

1、项目结构

2、pom.xml文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.demo</groupId>
  <artifactId>flowable</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>flowable-demo</name>
  <description>flowable</description>
  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.6.13</spring-boot.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- Flowable 工作流启动器(涵盖所有核心依赖) -->
    <dependency>
      <groupId>org.flowable</groupId>
      <artifactId>flowable-spring-boot-starter</artifactId>
      <version>6.8.0</version>
    </dependency>
    <!-- MySQL 驱动 -->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <scope>runtime</scope>
    </dependency>
    <!-- Lombok(可选,简化代码) -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <mainClass>cn.demo.flowabledemo.FlowableDemoApplication</mainClass>
          <skip>true</skip>
        </configuration>
        <executions>
          <execution>
            <id>repackage</id>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

3、application.yml文件

yml 复制代码
server:
  port: 8080 # 服务端口,可按需调整

spring:
  datasource:
    url: jdbc:mysql://192.168.247.155:3306/flowable?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
    username: root
    password: 123456 # 按你的实际密码填写
    driver-class-name: com.mysql.cj.jdbc.Driver

flowable:
  idm:
    enabled: false # 禁用Flowable自带用户表,采用自定义用户体系
  database-schema-update: true # 启动时自动建表

# 日志建议打开debug,方便排查
logging:
  level:
    org.flowable: debug

4、启动类

java 复制代码
@SpringBootApplication
public class FlowableApplication {

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

项目启动后,会在数据库创建相关数据表,一个简单的Flowable项目就创建了。

二、简单案例

1、安装Flowable BPMN visualizer插件

在IDEA上(2024.1)上安装Flowable BPMN visualizer插件,方便画流程图。

2、画一个请假流程图

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_leave" name="ask_leave" isExecutable="true">
    <startEvent id="sid-068031df-f9fc-4029-ac04-4a94e89923ec" name="请假开始">
      <documentation>请假开始</documentation>
    </startEvent>
    <userTask id="sid-25d6222a-de01-4498-8acc-15138ee214dd" name="提交请假单" flowable:assignee="${leaveUser}">
      <documentation>提交请假单</documentation>
    </userTask>
    <endEvent id="sid-522780b0-d1d7-4845-8efd-a9ba7783bbd3"/>
    <userTask id="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a" name="组长审核" flowable:assignee="${zuZhangUser}">
      <documentation>组长审核</documentation>
    </userTask>
    <userTask id="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7" name="经理审批" flowable:assignee="${managerUser}">
      <documentation>经理审批</documentation>
    </userTask>
    <exclusiveGateway id="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed"/>
    <sequenceFlow id="sid-c5e0dfe6-a547-4341-b7fa-14ba70850a5f" sourceRef="sid-068031df-f9fc-4029-ac04-4a94e89923ec" targetRef="sid-25d6222a-de01-4498-8acc-15138ee214dd"/>
    <sequenceFlow id="sid-1161af13-d962-4a84-927f-de2d050414c9" sourceRef="sid-25d6222a-de01-4498-8acc-15138ee214dd" targetRef="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a"/>
    <sequenceFlow id="sid-550f0471-28b4-4c44-b09f-790a07c8522c" sourceRef="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb" targetRef="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7" name="通过">
      <conditionExpression xsi:type="tFormalExpression">${zuZhangCheckResult == '通过'}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-8599e406-1f17-4ff8-ba74-582bb56afedb" sourceRef="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7" targetRef="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed">
      <conditionExpression/>
    </sequenceFlow>
    <sequenceFlow id="sid-f07e14ad-767c-4de1-942c-f4384a2273f7" sourceRef="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed" targetRef="sid-522780b0-d1d7-4845-8efd-a9ba7783bbd3" name="通过">
      <conditionExpression xsi:type="tFormalExpression">${managerCheckResult == '通过' }</conditionExpression>
    </sequenceFlow>
    <endEvent id="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6"/>
    <sequenceFlow id="sid-f2ccaabb-ee9d-48b4-9a23-3102abc4178b" sourceRef="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed" targetRef="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6">
      <conditionExpression xsi:type="tFormalExpression"/>
    </sequenceFlow>
    <exclusiveGateway id="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb"/>
    <sequenceFlow id="sid-1ba69f13-83bc-4dd9-a9e2-8ee99a430d59" sourceRef="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb" targetRef="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6">
      <conditionExpression xsi:type="tFormalExpression"/>
    </sequenceFlow>
    <sequenceFlow id="sid-82a1dce0-ff59-498b-93bc-a11329c20f2b" sourceRef="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a" targetRef="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_ask_leave">
    <bpmndi:BPMNPlane bpmnElement="ask_leave" id="BPMNPlane_ask_leave">
      <bpmndi:BPMNShape id="shape-bff7bf69-eaab-4ee5-b96a-593610d46c8f" bpmnElement="sid-068031df-f9fc-4029-ac04-4a94e89923ec">
        <omgdc:Bounds x="-175.0" y="-70.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-c5235275-7bf6-4e7a-b841-7eb44fcb0643" bpmnElement="sid-25d6222a-de01-4498-8acc-15138ee214dd">
        <omgdc:Bounds x="-95.0" y="-95.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-afe63443-5804-417d-8208-b46a5c315c98" bpmnElement="sid-522780b0-d1d7-4845-8efd-a9ba7783bbd3">
        <omgdc:Bounds x="525.0" y="-70.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-3e8b5cef-d42e-4a87-8c40-ad73fe98fae1" bpmnElement="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a">
        <omgdc:Bounds x="55.0" y="-95.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-f3c92d16-9acd-4309-a5d1-2986ee47220c" bpmnElement="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7">
        <omgdc:Bounds x="285.0" y="-95.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-c3d984d0-6771-4284-b7b5-757feb1a01b4" bpmnElement="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed">
        <omgdc:Bounds x="435.0" y="-75.0" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-1bf8a64e-79aa-4dcb-ae95-48b724fba5c7" bpmnElement="sid-c5e0dfe6-a547-4341-b7fa-14ba70850a5f">
        <omgdi:waypoint x="-145.0" y="-55.0"/>
        <omgdi:waypoint x="-95.0" y="-55.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-d048d099-592a-402e-9462-8456d456c401" bpmnElement="sid-1161af13-d962-4a84-927f-de2d050414c9">
        <omgdi:waypoint x="5.0" y="-55.0"/>
        <omgdi:waypoint x="55.0" y="-55.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-29c2de16-b663-4c20-ac2d-51b9ebb704a5" bpmnElement="sid-550f0471-28b4-4c44-b09f-790a07c8522c">
        <omgdi:waypoint x="229.99998" y="-55.0"/>
        <omgdi:waypoint x="285.0" y="-55.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-24766565-b1cd-4134-a2a7-01ab94c3f082" bpmnElement="sid-8599e406-1f17-4ff8-ba74-582bb56afedb">
        <omgdi:waypoint x="385.0" y="-55.0"/>
        <omgdi:waypoint x="435.0" y="-55.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-645307b8-39f9-43a0-bed8-c97a9f719c77" bpmnElement="sid-f07e14ad-767c-4de1-942c-f4384a2273f7">
        <omgdi:waypoint x="475.0" y="-55.0"/>
        <omgdi:waypoint x="525.0" y="-55.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-5779484f-f4da-43d2-ab2e-af76be09ee22" bpmnElement="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6">
        <omgdc:Bounds x="200.00002" y="50.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-c365dafb-0004-4362-9fb4-17a3a19b39e2" bpmnElement="sid-f2ccaabb-ee9d-48b4-9a23-3102abc4178b">
        <omgdi:waypoint x="455.0" y="-35.0"/>
        <omgdi:waypoint x="455.0" y="57.5"/>
        <omgdi:waypoint x="230.00002" y="57.5"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-a28f8193-b421-45cf-87fd-ba93be65fe9d" bpmnElement="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb">
        <omgdc:Bounds x="190.0" y="-75.0" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-37d4fbfc-f252-4e26-868e-2e8458fe9434" bpmnElement="sid-1ba69f13-83bc-4dd9-a9e2-8ee99a430d59">
        <omgdi:waypoint x="210.0" y="-35.0"/>
        <omgdi:waypoint x="207.50002" y="50.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-da617bee-5aef-408d-87c6-364dce76dc83" bpmnElement="sid-82a1dce0-ff59-498b-93bc-a11329c20f2b">
        <omgdi:waypoint x="155.0" y="-55.0"/>
        <omgdi:waypoint x="190.0" y="-55.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

3、Java代码

java 复制代码
@RestController
public class AskForLeaveController {
    @Autowired
    RuntimeService runtimeService;

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    TaskService taskService;

    @Autowired
    ProcessEngine processEngine;

    /**
     * 重新部署流程定义(清除旧版本)
     */
    @GetMapping("/redeploy")
    public String redeploy() {
        // 删除旧的流程定义部署
        List<org.flowable.engine.repository.Deployment> deployments = repositoryService.createDeploymentQuery()
                .processDefinitionKey("ask_leave")
                .list();
        for (org.flowable.engine.repository.Deployment deployment : deployments) {
            repositoryService.deleteDeployment(deployment.getId(), true);
        }

        // 重新部署新的流程定义
        repositoryService.createDeployment()
                .name("请假流程重新部署")
                .addClasspathResource("processes/ask_leave.bpmn20.xml")
                .deploy();

        return "流程重新部署完成,共删除 " + deployments.size() + " 个旧部署";
    }

    /**
     * 查看流程进度图
     * @param resp
     * @param processId 流程id
     * @throws Exception
     */
    @GetMapping("/viewFlowChart")
    public void viewFlowChart(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();
            }
        }
    }

    /**
     * 开启一个请假流程
     */
    @GetMapping("/askForLeave")
    public String askForLeave() {
        Map<String, Object> map = new HashMap<>();
        map.put("leaveUser", "张三");
        map.put("name", "ego");
        map.put("reason", "出去玩");
        map.put("days", 10);
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("ask_leave", map);
        return "开启请假流程 processId:" + instance.getId();
    }

    /**
     * 员工请假提交给组长审核
     */
    @GetMapping("/submitToZuZhang")
    public String submitToZuZhang() {
        // 员工查找到自己的任务,然后提交给组长审批
        List<Task> list = taskService.createTaskQuery().taskAssignee("张三").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            info = "任务 ID:" + task.getId() + ",任务申请人:" + task.getAssignee() + ",任务是否挂起:" + task.isSuspended();
            //提交给组长的时候,需要指定组长的id
            Map<String, Object> map = new HashMap<>();
            map.put("zuZhangUser", "李四");
            taskService.complete(task.getId(), map);
        }
        return info;
    }

    /**
     * 组长审核请假
     */
    @GetMapping("/zuZhangApprove")
    public String zuZhangApprove() {
        List<Task> list = taskService.createTaskQuery().taskAssignee("李四").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            if ("组长审核".equals(task.getName())) {
                info = "任务 ID:" + task.getId() + ",组长审核人:" + task.getAssignee() + ",任务是否挂起:" + task.isSuspended();
                // 提交给经理的时候,需要指定经理的id
                Map<String, Object> map = new HashMap<>();
                map.put("managerUser", "王五");
                map.put("zuZhangCheckResult", "通过");
                map.put("zuZhangUser", "李四");
                try {
                    taskService.complete(task.getId(), map);
                } catch (Exception e) {
                    e.printStackTrace();
                    info = "组长审核失败:" + task.getId() + " " + task.getAssignee();
                }
            }
        }
        return info;
    }

    /**
     * 经理审核通过
     */
    @GetMapping("/managerApprove")
    public String managerApprove() {
        List<Task> list = taskService.createTaskQuery().taskAssignee("王五").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            info = "经理:" + task.getAssignee() + ",审核通过:" + task.getId() + " 任务";
            Map<String, Object> map = new HashMap<>();
            map.put("managerCheckResult", "通过");
            taskService.complete(task.getId(), map);
        }
        return info;
    }

    /**
     * 经理审核拒绝
     */
    @GetMapping("/managerRefuse")
    public String managerRefuse() {
        List<Task> list = taskService.createTaskQuery().taskAssignee("王五").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            info = "经理:" + task.getAssignee() + ",审核拒绝:" + task.getId() + " 任务";
            Map<String, Object> map = new HashMap<>();
            map.put("managerCheckResult", "拒绝");
            taskService.complete(task.getId(), map);
        }
        return info;
    }
}

3、模拟请假

3.1、重新部署流程

url 复制代码
http://localhost:8080/redeploy

3.2、发起请假流程

url 复制代码
http://localhost:8080/askForLeave

3.3、查看流程图

url 复制代码
http://localhost:8080/viewFlowChart?processId=da61bee1-fb5b-11f0-8447-005056c00008

3.4、提交组长审批

url 复制代码
http://localhost:8080/submitToZuZhang

3.5、组长审批

url 复制代码
http://localhost:8080/zuZhangApprove

3.6、经理审批

url 复制代码
http://localhost:8080/managerApprove
相关推荐
A9better2 小时前
嵌入式开发学习日志50——任务调度与状态
stm32·嵌入式硬件·学习
非凡ghost3 小时前
ESET NupDown Tools 数据库下载工具
学习·软件需求
zzcufo3 小时前
多邻国第5阶段17-18学习笔记
笔记·学习
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
brave and determined4 小时前
工程设计类学习(DAY9):印刷电路板(PCB)材料选择、工艺特性与制造技术综合详解
学习·制造·pcb·smt·工程设计·fr-4·pcb钻孔
了一梨4 小时前
SQLite3学习笔记5:INSERT(写)+ SELECT(读)数据(C API)
笔记·学习·sqlite
青春男大5 小时前
Redis和RedisTemplate快速上手
java·数据库·redis·后端·spring·缓存
-To be number.wan5 小时前
算法学习日记 | 枚举
c++·学习·算法
jrlong5 小时前
DataWhale大模型基础与量化微调task5学习笔记(第 3 章:大模型训练与量化_模型量化实战)
笔记·学习
Sarvartha5 小时前
Routing(路由与分支)学习笔记
笔记·学习