Spring Boot 整合流程引擎 Flowable(附源码地址)

news2024/11/18 15:39:48

一、导入依赖

flowable依赖:

<dependency>
	<groupId>org.flowable</groupId>
	<artifactId>flowable-spring-boot-starter</artifactId>
	<version>6.7.2</version>
</dependency>

pom.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>flowable_study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.6</version>
    </parent>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>

        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>6.7.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

    </dependencies>

</project>

注意:以上为整个项目的pom.xml文件,根据自己实际情况修改,如果只想在现有环境加入Flowable,那么直接引入最上方的Flowable依赖即可


二、数据库配置

Flowable需要连接数据库,并会在项目启动的时候新建表。因此我们在application.yml中配置:

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: 199692
    url: jdbc:mysql://127.0.0.1:3306/flowable?serverTimezone=Asia/Shanghai&useSSL=false

配置成功后,我们启动项目时,会自动给我们建表,如图:

在这里插入图片描述


三、下载插件 Flowable BPMN visualizer

如果IDE选择的是IDEA的话,可以下载插件 Flowable BPMN visualizer。
快捷键 Ctrl + Alt + S 打开设置,找到 Plugins,选择 Marketplace,输入 Flowable BPMN visualizer,最后点击安装。
在这里插入图片描述

安装完成后点击 Apply 应用,最后点击 OK 关闭窗口。

四、绘图

我们在项目的resources目录下,新建文件,能看到多了一个 New Flowable BPMN 2.0 file 选项,我们选择它,新建一个请假流程的文件,文件名为:ask_for_leave,可根据自己业务命名。

在这里插入图片描述

新建过后,我们右击文件,选择 View BPMN (Flowable) Diagram
在这里插入图片描述

打开可视化界面,开始画流程图。

右击空白处可以新建各种各样的事件和流程。

在这里插入图片描述

如图为一个简单的请假流程示意图:

在这里插入图片描述

画图的时候,我们需要在下方更改它的一些属性,如:ID、Name等,便于我们后面代码见名知意。
画好后,我们可以看到会自动给我们生成文件内容:

<?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="start_leave" name="开始请假"/>
    <userTask id="employee" name="员工" flowable:assignee="#{employee}"/>
    <sequenceFlow id="flow_start" sourceRef="start_leave" targetRef="employee"/>
    <userTask id="leader" name="组长" flowable:assignee="#{leader}"/>
    <sequenceFlow id="leave" sourceRef="employee" targetRef="leader" name="请假"/>
    <exclusiveGateway id="leader_judge"/>
    <sequenceFlow id="leader_audit" sourceRef="leader" targetRef="leader_judge" name="组长审批"/>
    <serviceTask id="send_message" flowable:exclusive="true" name="发送请假失败消息" flowable:class="src.com.dxc.service.AskForLeaveFail"/>
    <sequenceFlow id="leader_reject" sourceRef="leader_judge" targetRef="send_message" name="驳回">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='驳回'}]]></conditionExpression>
    </sequenceFlow>
    <userTask id="manager" name="总经理" flowable:assignee="#{manager}"/>
    <sequenceFlow id="leader_agree" sourceRef="leader_judge" targetRef="manager" name="同意">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='同意'}]]></conditionExpression>
    </sequenceFlow>
    <endEvent id="请假失败" name="leave_fail"/>
    <sequenceFlow id="flow_fail_end" sourceRef="send_message" targetRef="请假失败"/>
    <exclusiveGateway id="manager_judge"/>
    <sequenceFlow id="manager_audit" sourceRef="manager" targetRef="manager_judge" name="经理审批"/>
    <sequenceFlow id="manager_rejet" sourceRef="manager_judge" targetRef="send_message" name="驳回">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='驳回'}]]></conditionExpression>
    </sequenceFlow>
    <endEvent id="请假成功" name="leave_success"/>
    <sequenceFlow id="manager_agree" sourceRef="manager_judge" targetRef="请假成功" name="同意">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='同意'}]]></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-ad5ec363-de3b-4ede-883e-629cbe497515" bpmnElement="start_leave">
        <omgdc:Bounds x="-555.0" y="-580.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-0d5a8676-83b3-46ea-a710-b73fb38a6ded" bpmnElement="employee">
        <omgdc:Bounds x="-452.44943" y="-605.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-bf2b83dc-ad4f-4ffa-ba9d-82d8128c2dde" bpmnElement="flow_start">
        <omgdi:waypoint x="-525.0" y="-565.0"/>
        <omgdi:waypoint x="-485.0497" y="-565.0"/>
        <omgdi:waypoint x="-452.44943" y="-565.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-d00296f1-1a06-4052-bd3c-d373c1f9ad74" bpmnElement="leader">
        <omgdc:Bounds x="-260.0" y="-605.00006" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-6a821f76-9583-438b-810e-5a8b48466ebb" bpmnElement="leave">
        <omgdi:waypoint x="-352.44943" y="-565.0"/>
        <omgdi:waypoint x="-260.0" y="-565.00006"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-57050807-ffe2-4a4d-a60b-eb224575ef4b" bpmnElement="leader_judge">
        <omgdc:Bounds x="-75.0" y="-585.0001" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-795a0427-2f83-4607-a40d-4b359790275e" bpmnElement="leader_audit">
        <omgdi:waypoint x="-160.0" y="-565.00006"/>
        <omgdi:waypoint x="-75.0" y="-565.0001"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-35bcb335-0516-4c89-9148-ead6c956472d" bpmnElement="send_message">
        <omgdc:Bounds x="-105.0" y="-465.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-c6b5f535-d7f2-4816-8d3a-e8499ad0c923" bpmnElement="leader_reject">
        <omgdi:waypoint x="-55.0" y="-545.0001"/>
        <omgdi:waypoint x="-55.0" y="-465.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-79a775c0-5c7a-4adf-b544-9954eedb08f6" bpmnElement="manager">
        <omgdc:Bounds x="50.0" y="-605.0001" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-57cd436f-f129-4044-b65c-637cf63465a6" bpmnElement="leader_agree">
        <omgdi:waypoint x="-35.0" y="-565.0001"/>
        <omgdi:waypoint x="50.0" y="-565.0001"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-37759eb0-7cf1-4da0-8aba-39c8443675ac" bpmnElement="请假失败">
        <omgdc:Bounds x="-240.0" y="-440.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-3acbccbb-9a56-4ff0-86b1-f2f6641f125c" bpmnElement="flow_fail_end">
        <omgdi:waypoint x="-105.0" y="-425.0"/>
        <omgdi:waypoint x="-210.0" y="-425.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-b3da428e-37fc-410e-a90f-40eb0230e892" bpmnElement="manager_judge">
        <omgdc:Bounds x="250.0" y="-585.0001" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-e56b296d-0690-44e3-bdaf-6e3864a826ca" bpmnElement="manager_audit">
        <omgdi:waypoint x="150.0" y="-565.0001"/>
        <omgdi:waypoint x="250.0" y="-565.0001"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-a806347f-3f50-49da-ab26-d571a6104c0c" bpmnElement="manager_rejet">
        <omgdi:waypoint x="270.0" y="-545.0001"/>
        <omgdi:waypoint x="269.99997" y="-424.99997"/>
        <omgdi:waypoint x="-5.0" y="-425.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-416e3b9e-2e1a-489a-808e-fa5fd3f8c691" bpmnElement="请假成功">
        <omgdc:Bounds x="395.0" y="-580.00006" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-33f48358-b60a-4ea8-bd80-9729cfdf6a11" bpmnElement="manager_agree">
        <omgdi:waypoint x="290.0" y="-565.0001"/>
        <omgdi:waypoint x="395.0" y="-565.00006"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

不想画的小伙伴可以直接复制我的内容。
这个文件中,我们只需要关注被 <process> </process> 标签包起来的内容,这是一个完整的流程定义。
其实XML的内容非常容易理解,根据标签和流程图来对照看,可以知道具体的含义:

  • <process> : 表示一个完整的工作流程。
  • <startEvent> : 工作流中起点位置,也就是图中的绿色按钮。
  • <endEvent> : 工作流中结束位置,也就是图中的红色按钮。
  • <userTask> :代表一个任务审核节点(组长、经理等角色),这个节点上有一个 flowable:assignee 属性,这表示这个节点该由谁来处理,将来在 Java 代码中调用的时候,我们需要指定对应的处理人的 ID 或者其他唯一标记。
  • <serviceTask>:这是服务任务,在具体的实现中,这个任务可以做任何事情,这里我们使用flowable:class 属性配置了一个类,表示如果走到该节点则自动执行该类中的方法
  • <exclusiveGateway> :逻辑判断节点,相当于流程图中的菱形框。
  • <sequenceFlow> :链接各个节点的线条,sourceRef 属性表示线的起始节点,targetRef 属性表示线指向的节点,我们图中的线条都属于这种。

根据上述对每个标签的解释,能够很轻易的看懂生成出来的XML文件。


五、代码测试

Gitee代码地址:https://gitee.com/du_xin_cheng/flowable_study

启动类:

package src.com.dxc;

import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.DeploymentBuilder;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.Resource;
import java.io.InputStream;

/**
 * 启动类
 *
 * @Author xincheng.du
 * @Date 2023/5/22 16:50
 */
@SpringBootApplication
public class Application implements CommandLineRunner {

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

    @Resource
    private RepositoryService repositoryService;

    /**
     * 项目启动加载流程文件
     *
     * @param args  args
     */
    @Override
    public void run(String... args)  {
        InputStream inputStream = this.getClass().getResourceAsStream("/ask_for_leave.bpmn20.xml");
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
        deploymentBuilder.addInputStream("ask_for_leave.bpmn20.xml", inputStream);
        deploymentBuilder.deploy();
    }
}

发起请假流程参数类 AskForLeaveStartDTO:

package src.com.dxc.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 发起请假流程参数
 *
 * @Author xincheng.du
 * @Date 2023/5/23 15:00
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AskForLeaveStartDTO {

    /**
     * 员工id
     */
    private String employeeId;

    /**
     * 员工姓名
     */
    private String name;

    /**
     * 请假原因
     */
    private String reason;

    /**
     * 请假天数
     */
    private Integer days;

}

审核参数类 AuditDTO:

package src.com.dxc.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 审核参数
 *
 * @Author xincheng.du
 * @Date 2023/5/23 15:12
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuditDTO {

    /**
     * 下级id {员工id || 组长id}
     */
    private String lowerId;

    /**
     * 下级名称 {employee || leader}
     * 此处的名称对应 ask_for_leave.bpmn20.xml 文件中<userTask>标签下的 flowable:assignee 属性
     */
    private String lowerName;

    /**
     * 上级id {组长id || 经理id}
     */
    private String superiorId;

    /**
     * 上级名称 {leader || manager}
     * 此处的名称对应 ask_for_leave.bpmn20.xml 文件中<userTask>标签下的 flowable:assignee 属性
     */
    private String superiorName;

    /**
     * 审核结果 {同意 || 驳回}
     */
    private String checkResult;

    /**
     * 是否为最上级
     * true->leaderId为最上级,managerId为空
     * false->leaderId不为最上级,managerId为leaderId的上级
     */
    private Boolean isTop;

}

请假流程接口 AskForLeaveService:

package src.com.dxc.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Service;
import src.com.dxc.model.AskForLeaveStartDTO;
import src.com.dxc.model.AuditDTO;
import src.com.dxc.service.AskForLeaveService;

import java.util.*;

/**
 * 请假流程
 *
 * @Author xincheng.du
 * @Date 2023/5/23 14:51
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AskForLeaveServiceImpl implements AskForLeaveService {

    private final RuntimeService runtimeService;

    private final TaskService taskService;

    public static final String EMPLOYEE = "employee";

    public static final String FLOW_KEY = "ask_for_leave";

    public static final String NAME = "name";

    public static final String REASON = "reason";

    public static final String DAYS = "days";

    public static final String CHECK_RESULT = "checkResult";

    @Override
    public void askForLeave(AskForLeaveStartDTO dto) {
        HashMap<String, Object> map = new HashMap<>();
        map.put(EMPLOYEE, dto.getEmployeeId());
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(FLOW_KEY, map);
        runtimeService.setVariable(processInstance.getId(), NAME, dto.getName());
        runtimeService.setVariable(processInstance.getId(), REASON, dto.getReason());
        runtimeService.setVariable(processInstance.getId(), DAYS, dto.getDays());
        log.info("创建请假流程 processId:{}", processInstance.getId());
    }

    @Override
    public void executeProcess(AuditDTO dto) {
        // 查找到下级的任务,然后提交给上级审批
        List<Task> list = taskService.createTaskQuery().taskAssignee(dto.getLowerId()).orderByTaskId().desc().list();
        for (Task task : list) {
            log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
            Map<String, Object> map = new HashMap<>();
            // 如果当前处理人不为最顶级处理人,那么需要指定当前处理人的上级id
            Optional.of(dto.getIsTop()).ifPresent(isTop -> {
                if (Objects.equals(Boolean.FALSE, isTop)) {
                    map.put(dto.getSuperiorName(), dto.getSuperiorId());
                }
            });
            Optional.ofNullable(dto.getCheckResult()).ifPresent(result -> map.put(CHECK_RESULT, result));
            taskService.complete(task.getId(), map);
        }
    }
}

请假流程实现类 AskForLeaveServiceImpl:

package src.com.dxc.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Service;
import src.com.dxc.model.AskForLeaveStartDTO;
import src.com.dxc.model.AuditDTO;
import src.com.dxc.service.AskForLeaveService;

import java.util.*;

/**
 * 请假流程
 *
 * @Author xincheng.du
 * @Date 2023/5/23 14:51
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AskForLeaveServiceImpl implements AskForLeaveService {

    private final RuntimeService runtimeService;

    private final TaskService taskService;

    public static final String EMPLOYEE = "employee";

    public static final String FLOW_KEY = "ask_for_leave";

    public static final String NAME = "name";

    public static final String REASON = "reason";

    public static final String DAYS = "days";

    public static final String CHECK_RESULT = "checkResult";

    @Override
    public void askForLeave(AskForLeaveStartDTO dto) {
        HashMap<String, Object> map = new HashMap<>();
        map.put(EMPLOYEE, dto.getEmployeeId());
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(FLOW_KEY, map);
        runtimeService.setVariable(processInstance.getId(), NAME, dto.getName());
        runtimeService.setVariable(processInstance.getId(), REASON, dto.getReason());
        runtimeService.setVariable(processInstance.getId(), DAYS, dto.getDays());
        log.info("创建请假流程 processId:{}", processInstance.getId());
    }

    @Override
    public void executeProcess(AuditDTO dto) {
        // 查找到下级的任务,然后提交给上级审批
        List<Task> list = taskService.createTaskQuery().taskAssignee(dto.getLowerId()).orderByTaskId().desc().list();
        for (Task task : list) {
            log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
            Map<String, Object> map = new HashMap<>();
            // 如果当前处理人不为最顶级处理人,那么需要指定当前处理人的上级id
            Optional.of(dto.getIsTop()).ifPresent(isTop -> {
                if (Objects.equals(Boolean.FALSE, isTop)) {
                    map.put(dto.getSuperiorName(), dto.getSuperiorId());
                }
            });
            Optional.ofNullable(dto.getCheckResult()).ifPresent(result -> map.put(CHECK_RESULT, result));
            taskService.complete(task.getId(), map);
        }
    }
}

请假失败执行类 AskForLeaveFail:

package src.com.dxc.service;

import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

@Slf4j
public class AskForLeaveFail implements JavaDelegate {

    @Override
    public void execute(DelegateExecution execution) {
        log.info("请假失败。。。");
    }

}

查看流程进度Controller,通过启动项目调用接口查看:

package src.com.dxc.controller;

import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.springframework.web.bind.annotation.GetMapping;
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.List;

/**
 * 查看流程进度
 *
 * @Author xincheng.du
 * @Date 2023/5/23 15:25
 */
@RestController
@RequiredArgsConstructor
public class ShowController {

    private final RuntimeService runtimeService;

    private final RepositoryService repositoryService;

    private final ProcessEngine processEngine;

    @GetMapping("/showFlowPic")
    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();
            }
        }
    }
}

流程测试类 AskForLeaveServiceImplTest :

package src.com.dxc.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import src.com.dxc.Application;
import src.com.dxc.model.AskForLeaveStartDTO;
import src.com.dxc.model.AuditDTO;
import src.com.dxc.service.AskForLeaveService;

@Slf4j
@SpringBootTest(classes = Application.class)
class AskForLeaveServiceImplTest {

    @Autowired
    public AskForLeaveService askForLeaveService;

    /**
     * 发起请假流程
     */
    @Test
    void askForLeave() {
        AskForLeaveStartDTO dto = new AskForLeaveStartDTO();
        dto.setEmployeeId("1");
        dto.setName("xiaoMing");
        dto.setReason("旅游");
        dto.setDays(10);
        askForLeaveService.askForLeave(dto);
    }

    /**
     * 员工提交流程到组长处
     */
    @Test
    void submit() {
        AuditDTO dto = new AuditDTO();
        dto.setLowerId("1");
        dto.setLowerName("employee");
        dto.setSuperiorId("2");
        dto.setSuperiorName("leader");
        dto.setIsTop(false);
        askForLeaveService.executeProcess(dto);
    }

    /**
     * 组长审批 - 同意
     */
    @Test
    void leaderAgree() {
        AuditDTO dto = new AuditDTO();
        dto.setLowerId("2");
        dto.setLowerName("leader");
        dto.setSuperiorId("3");
        dto.setSuperiorName("manager");
        dto.setCheckResult("同意");
        dto.setIsTop(false);
        askForLeaveService.executeProcess(dto);
    }

    /**
     * 组长审批 - 驳回
     */
    @Test
    void leaderReject() {
        AuditDTO dto = new AuditDTO();
        dto.setLowerId("2");
        dto.setLowerName("leader");
        dto.setSuperiorId("3");
        dto.setSuperiorName("manager");
        dto.setCheckResult("驳回");
        dto.setIsTop(false);
        askForLeaveService.executeProcess(dto);
    }

    /**
     * 经理审批 - 同意
     */
    @Test
    void managerAgree() {
        AuditDTO dto = new AuditDTO();
        dto.setLowerId("3");
        dto.setLowerName("manager");
        dto.setCheckResult("同意");
        dto.setIsTop(true);
        askForLeaveService.executeProcess(dto);
    }

    /**
     * 经理审批 - 驳回
     */
    @Test
    void managerReject() {
        AuditDTO dto = new AuditDTO();
        dto.setLowerId("3");
        dto.setLowerName("manager");
        dto.setCheckResult("驳回");
        dto.setIsTop(true);
        askForLeaveService.executeProcess(dto);
    }
}

每走一步都可以通过访问接口 /showFlowPic 查看到当前进度是否正确,接口中所需的processIdaskForLeave() 方法打印出的 processId

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/563213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驱动页面性能优化的3个有效策略

目录 引言 背景 前端性能优化 测试视角的解法 性能问题的发现 性能数据的采集 性能指标的确定 性能问题的分析 如何衡量性能问题严重性 分析性能瓶颈-分析思路 分析结论关键思路 引言 测试通过发现、分析、验证三板斧&#xff0c;驱动推进页面性能优化快速有效&…

关注 | 蛙色元宇宙,正式成为XRMA联盟成员单位

中国虚拟现实与元宇宙产业峰会&#xff0c;2023年3月22日于杭州圆满结束&#xff0c;在杭州市人民政府、浙江省经济和信息化厅指导&#xff0c;由杭州市经济和信息化局、杭州市西湖区人民政府主办&#xff0c;中国信息通信研究院承办。 蛙色元宇宙作为元宇宙的领先企业之一&…

【AI面试】CrossEntropy Loss 、Balanced Cross Entropy、 Dice Loss 和 Focal Loss 横评对比

样本不均衡问题一直是深度学习领域一个不可忽略的问题&#xff0c;常说的长尾效应&#xff0c;说的就是这个问题。一类占据了主导地位&#xff0c;导致其他类无论怎么优化&#xff0c;都不能好转。 无论是纯纯的分类任务&#xff0c;还是稍微复杂一些的目标检测任务和分割任务…

关于java在成员/全局变量上不同类型赋值遇到的问题(值传递)

一个疑惑 文件简介回答参考文献 文件简介 class ss{static class Student{int id;String name; /*public Student(int id, String name) {this.id id;this.name name;}*/public int getId() {return id;}public void setId(int id) {this.id id;}public String getName() {…

数字人入门文章速览

语音驱动三维人脸方法 OPPO 数字人语音驱动面部技术实践 【万字长文】虚拟人漫谈 Blendshape学习笔记 人脸重建速览&#xff0c;从3DMM到表情驱动动画 功能强大的python包&#xff08;四&#xff09;&#xff1a;OpenCV 从Blendshapes到Animoji 3D人脸重建算法汇总 一、3D人脸重…

windows 10 安装k8s环境 Kubernetes

主要命令有 1. iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex 2. choco install minikube 3. minikube start 4. minikube dashboard 使用管理员运行 PowerShell 执行下面这条命令 iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex choc…

开源赋能 普惠未来|铜锁/Tongsuo诚邀您参与2023开放原子全球开源峰会

铜锁/Tongsuo是一个提供现代密码学算法和安全通信协议的开源基础密码库&#xff0c;为存储、网络、密钥管理、隐私计算、区块链等诸多业务场景提供底层的密码学基础能力&#xff0c;实现数据在传输、使用、存储等过程中的私密性、完整性和可认证性&#xff0c;为数据生命周期中…

Linux:web基础与HTTP协议

Linux&#xff1a;web基础与HTTP协议 一、域名概述1.1 域名的概念1.2 域名空间结构1.3 域名注册 二、网页的概念2.1 网页2.2 网站2.3 主页2.4 网页2 三、HTML概述3.1 HTML概述3.2 HTML文档结构3.3 HTML 基本标签 四、web概述4.1 web概述4.2 Web1.0 vs Web2.04.3 静态网页4.3.1 …

【挑战自己】软件测试的7个级别,做到3级已经超越80%测试人

有人说&#xff1a;软件测试就是最low的点点点工作。 有人说&#xff1a;测试工作职位薪水到一定程度只能原地踏步无法提升 也有人说&#xff1a;测试行业相对于开发来说技术性很低&#xff0c;容易被取代。 这其实是对测试行业最大的误解。测试可深可浅&#xff0c;可窄可广…

QDir拼接路径解决各种斜杠问题

一般在项目中经常需要组合路径,与其他程序进行相互调用传递消息通信。 经常可能因为多加斜杠、少加斜杠等问题导致很多问题。 为了解决这些问题,我们可以使用QDir来完成路径的拼接,不直接拼接字符串。 QDir的静态方法QDir::cleanPath() 是为了规范化路径名的,在使用QDir组…

Unity第三方分享(微信)插件ShareSDK使用简记

Unity第三方分享&#xff08;微信&#xff09;插件ShareSDK使用简记 微信分享遇到的问题记录 链接官方链接参考链接 微信分享 官方文档&#xff1a;MobTech集成文档-MobTech 下载地址&#xff1a;GitHub - MobClub/New-Unity-For-ShareSDK: New sample of ShareSDK for Unity,…

ChatGPT:你真的了解网络安全吗?浅谈攻击防御进行时之网络安全新防御

ChatGPT&#xff1a;你真的了解网络安全吗&#xff1f;浅谈网络安全攻击防御进行时 网络安全新防御1. 针对人工智能2. 针对5G和物联网3. 针对云安全4.针对社交工程5. 针对加密技术6. 针对多层次的安全控制 总结 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-traine…

去除氟离子的最好办法,矿井水现场氟离子超标RO浓水除氟

一、产品介绍 氟化物选择吸附树脂 Tulsimer CH-87 是一款去除水溶液中氟离子的专用的凝胶型选择性离子交换树脂。它是具有氟化物选择性官能团的交联聚苯乙烯共聚物架构的树脂。 去除氟离子的能力可以达到 1ppm 以下的水平。中性至碱性的PH范围内有较好的工作效率&#xff0c;并…

保姆级别!!!--全网绝对教你会!!教你如何使用MQTTFX连接阿里云平台中的设备----下期告诉你如何创建!

本期需要下载的软件 MQttfx安装包&#xff0c;本人打包的-嵌入式文档类资源-CSDN文库 目录 第一步&#xff1a;建造阿里云设备 这个可以先忽略建造步骤&#xff0c;下期将提供步骤。 第二步&#xff1a;下载mqttfx软件 第三步&#xff1a;填写密钥信息进行连接 查看三元…

如何显示物品词缀?

UE5 插件开发指南 前言0 什么是物品词缀?1 如何动态显示物品词缀?前言 读到这里读者应该已经知道如何解析这个题目了,拆分为如下问题: (1)什么是物品词缀? (2)如何动态显示? 0 什么是物品词缀? 首先要知道什么是物品词缀,如下图所示,物品词缀就是用来描述物品属性的…

助力服务智能医疗检测,基于yolov5开发构建结直肠息肉检测系统,实践训练n/s/m不同量级模型,对比性能结果

将人工智能技术应用于众多的生活真实场景中是一件很有前景的事情&#xff0c;在我前面的博文中已经有不少的相关的开发实践&#xff0c;应用于医学领域也是一个非常重要的细分分支领域&#xff0c;在前面的博文中也有一些实践&#xff0c;感兴趣的话可以自行移步阅读。 《服务…

无需公网IP,快速远程登录家里的威联通NAS

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 转载自cpolar内网穿透的文章&#xff1a;无需公网IP&#xff0c;在外远程访问威联通QNAP|N…

『 MySQL篇 』:MySQL 事务特性

目录 一 . 什么是事务&#xff1f; 二. 事务的操作 ​三. 事务的四大特性 四 . 并发事务可能产生的问题 五 . 数据库的隔离级别 一 . 什么是事务&#xff1f; 从概念上来讲&#xff0c;事务是一个有限的数据库操作序列构成&#xff0c;这些操作要么全部执行&#xff0c…

使用 RLHF 训练 LLaMA 的实践指南:StackLLaMA

由于LLaMA没有使用RLHF&#xff0c;后来有一个初创公司 Nebuly AI使用LangChain agent生成的数据集对LLaMA模型使用了RLHF进行学习&#xff0c;得到了ChatLLaMA模型&#xff0c;详情请参考&#xff1a;Meta开源的LLaMA性能真如论文所述吗&#xff1f;如果增加RLHF&#xff0c;效…

Xcode14.3升级完项目无法运行

升级到14.3在真机上运行报错如下: /XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a 解决方法: 在Podfile中的最后一个end出添加下面代码 post_install do |installer| installer.generated_projects.each do |project| project.targets.each do |target| targ…