SpringBoot集成Flowable
一、工作流介绍
1、概念
通过计算机对业务流程的自动化管理。工作流是建立在业务流程的基础上,一个软件的系统核心根本上还是系统的业务流程,工作流只是协助进行业务流程管理。
解决的是:在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标
2、工作流系统
概念:具有工作流功能的系统
比如,OA、ERP系统,可能涉及工作流,都可以叫工作流系统
3、具体应用
关键业务流程:订单、报价处理、合同审核、客户电话处理、供应链管理等
行政管理类:出差申请、加班申请、请假申请、用车申请、各种办公用品申请、购买申请、日报周报等凡是原来手工流转处理的行政表单。
人事管理类:员工培训安排、绩效考评、职位变动处理、员工档案信息管理等。
财务相关类:付款请求、应收款处理、日常报销处理、出差报销、预算和计划申请等。
客户服务类:客户信息管理、客户投诉、请求处理、售后服务管理等。
特殊服务类:SO系列对应流程、质量管理对应流程、产品数据信息管理、贸易公司报关处理、物流公司货物跟踪处理等各种通过表单逐步手工流转完成的任务均可应用工作流软件自动规范地实施。
4、实现方式对比
原始方式:就是采用状态值来跟踪流程的变化,通过这个值去决定不同用户是否展示,耦合度高
新的工作流引擎,可以灵活调整,实现简单
二、Flowable概述
具体参考官网:https://tkjohn.github.io/flowable-userguide/#_introduction
1、介绍
Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。
2、BPM
业务流程管理,一种规范化的构造端到端的业务流程,提高组织业务效率。
3、BPMN
业务流程模型和符号,由BPMI 开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。
一般就是用来画我们需要的流程图。
常用符号
网关
互斥网关(Exclusive Gateway) 又称排他网关,他有且仅有一个有效出口,可以理解为if…else if… else if…else,就和我们平时写代码的一样。
并行网关(Parallel Gateway) 他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。
包容性网关(Inclusive Gateway)只要满足条件的出口都会执行,可以理解为 if(…) do, if (…) do, if (…) do,所有的条件判断都是同级别的。
任务
人工任务(User Task)
它是使用的最多的一种任务类型,他自带有一些人工任务的变量,例如签收人(Assignee),签收人就代表该任务交由谁处理,我们也可以通过某个特定或一系列特定的签收人来查找待办任务。利用上面的行为解释便是,当到达User Task节点的时候,节点设置Assignee变量或等待设置Assignee变量,当任务被完成的时候,我们使用Trigger来要求流程引擎退出该任务,继续流转。服务任务(Service Task)
该任务会在到达的时候执行一段自动的逻辑并自动流转。从“到达自动执行一段逻辑”这里我们就可以发现,服务任务的想象空间就可以非常大,我们可以执行一段计算,执行发送邮件,执行RPC调用,而使用最广泛的则为HTTP调用,因为HTTP是使用最广泛的协议之一,它可以解决大部分第三方调用问题,在我们的使用中,HTTP服务任务也被我们单独剥离出来作为一个特殊任务节点。接受任务(Receive Task)
该任务的名字让人费解,但它又是最简单的一种任务,当该任务到达的时候,它不做任何逻辑,而是被动地等待Trigger,它的适用场景往往是一些不明确的阻塞,比如:一个复杂的计算需要等待很多条件,这些条件是需要人为来判断是否可以执行,而不是直接执行,这个时候,工作人员如果判断可以继续了,那么就Trigger一下使其流转。
结构
调用活动(Call Activity)
调用活动可以理解为函数调用,它会引用另外一个流程使之作为子流程运行,调用活动跟函数调用的功能一样,使流程模块化,增加复用的可能性。
一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。
4、为什么选择Flowable?
修复了activiti6很多的bug,可以实现零成本从activiti迁移到flowable。flowable目前已经支持加签、动态增加实例中的节点、支持cmmn、dmn规范。这些都是activiti6目前版本没有的。
flowable已经支持所有的历史数据使用mongdb存储,activiti没有。
flowable支持事务子流程,activiti没有。
flowable支持多实例加签、减签,activiti没有。
flowable支持httpTask等新的类型节点,activiti没有。
flowable支持在流程中动态添加任务节点,activiti没有。
flowable支持历史任务数据通过消息中间件发送,activiti没有。
flowable支持java11,activiti没有。
flowable支持动态脚本,,activiti没有。
flowable支持条件表达式中自定义juel函数,activiti没有。
flowable支持cmmn规范,activiti没有。
flowable修复了dmn规范设计器,activit用的dmn设计器还是旧的框架,bug太多。
flowable屏蔽了pvm,activiti6也屏蔽了pvm(因为6版本官方提供了加签功能,发现pvm设计的过于臃肿,索性直接移除,这样加签实现起来更简洁、事实确实如此,如果需要获取节点、连线等信息可以使用bpmnmodel替代)。
flowable与activiti提供了新的事务监听器。activiti5版本只有事件监听器、任务监听器、执行监听器。
flowable对activiti的代码大量的进行了重构。
activiti以及flowable支持的数据库有h2、hsql、mysql、oracle、postgres、mssql、db2。其他数据库不支持的。使用国产数据库的可能有点失望了,需要修改源码了。
flowable支持jms、rabbitmq、mongodb方式处理历史数据,activiti没有。
5、 Flowable基础表结构
工作流程的相关操作都是操作存储在对应的表结构中,为了能更好的弄清楚Flowable的实现原理和细节,我们有必要先弄清楚Flowable的相关表结构及其作用。在Flowable中的表结构在初始化的时候会创建五类表结构,具体如下:
ACT_RE :'RE'表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RU:'RU'表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Flowable只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI:'HI'表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE: GE 表示 general。 通用数据, 用于不同场景下
ACT_ID: ’ID’表示identity(组织机构)。这些表包含标识的信息,如用户,用户组,等等。
Flowable在项目启动的时候会自动创建表,以下是主要几张表介绍
ACT_RU_TASK:每次启动的流程都会在这张表中,表示代办项,流程结束会删除该流程数据
ACT_RU_EXECUTION:流程执行过程表,会存该流程正在执行的过程数据,流程结束会删除该流程数据
ACT_RU_VARIABLE:流程变量表,流程中传的参数都会在该表存储,流程结束会删除该流程数据
ACT_HI_PROCINST:历史运行流程,当流程处理完了, 在ACT_RU_* 表中就不会有数据, 可以在该表中查询历史
ACT_HI_TASKINST:历史运行的task信息,
ACT_RE_PROCDEF:流程模板记录,同一个key多次发布version_字段会递增
ACT_RE_DEPLOYMENT:部署的流程模板,可以启动流程使用的
三、创建项目
创建一个maven项目,引入springboot、mysql、flowable
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>com.test</groupId><artifactId>flowable-test</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.0.RELEASE</version><relativePath/></parent><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><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><!--flowable--><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.4.1</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.22</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version></dependency></dependencies>
</project>
application.yml
注意mysql数据库驱动版本差异
- com.mysql.jdbc.Driver:
这是 旧版 MySQL JDBC 驱动的类名。
该驱动属于 mysql-connector-java 5.x 或更早版本。
它是 MySQL 5.x 系列及之前的 JDBC 驱动程序的标准类名。
从 MySQL 5.1 到 MySQL 5.7 版本,使用的是这个驱动。
- com.mysql.cj.jdbc.Driver:
这是 新版 MySQL JDBC 驱动的类名。
该驱动是 MySQL 8.x 及之后版本的标准 JDBC 驱动程序。
com.mysql.cj.jdbc.Driver 是从 mysql-connector-java 8.x 开始引入的。
com.mysql.cj 代表 "Connector/J" 的缩写,cj 指代的是 Connector/J 新的模块命名空间。
spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/flowable-test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driver
flowable:# 业务流程设计的表自动创建database-schema-update: true# 异步执行耗时操作,提升性能async-executor-activate: false
FlowableApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class FlowableApplication {public static void main(String[] args) {SpringApplication.run(FlowableApplication.class, args);}
}
运行并启动项目,会在数据库自动生成工作流所使用的表
四、画流程图
这里使用 IDEA 插件 Flowable BPMN visualizer,如下图:
装好插件之后,在 resources 目录下新建 processes 目录,这个目录下的流程文件将来会被自动部署
新建一个ask_for_leave.bpmn20.xml文件(.bpmn20.xml是固定文件后缀,不需要填写)
文件创建出来之后,右键单击,选择 View BPMN(Flowable) Diagram,就打开了此文件的可视化页面了,然后就可以来绘制自己的流程图
然后在可视化页面空白处,右键点击,就可以添加不同的符号,来绘制流程图
绘制完成
我们来看下这个流程对应的 XML 文件,一些流程细节会在 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="startLeave"/><userTask id="leaveTask" name="请假" flowable:assignee="#{leaveUser}"/><sequenceFlow id="flowStart" sourceRef="startLeave" targetRef="leaveTask"/><userTask id="zuZhangTask" name="组长审核" flowable:assignee="#{zuZhangUser}"/><sequenceFlow id="modeFlow" sourceRef="leaveTask" targetRef="zuZhangTask"/><exclusiveGateway id="zuZhangJudgeGateway"/><sequenceFlow id="zuZhang_go_judge" sourceRef="zuZhangTask" targetRef="zuZhangJudgeGateway"/><userTask id="managerTask" name="经理审核" flowable:assignee="#{managerUser}"/><sequenceFlow id="zuZhangPass" sourceRef="zuZhangJudgeGateway" targetRef="managerTask" name="组长审核通过"><conditionExpression xsi:type="tFormalExpression">${var:equals(zuZhangCheckResult,"通过")}</conditionExpression></sequenceFlow><endEvent id="endLeave"/><exclusiveGateway id="managerJudgeGateway"/><sequenceFlow id="manager_go_judge" sourceRef="managerTask" targetRef="managerJudgeGateway"/><sequenceFlow id="managerPass" sourceRef="managerJudgeGateway" targetRef="endLeave" name="经理审核通过"><conditionExpression xsi:type="tFormalExpression">${var:equals(managerCheckResult,"通过")}</conditionExpression></sequenceFlow><serviceTask id="sendFailMessage" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="com.test.service.LeaveFailService"/><sequenceFlow id="zuZhangRefuse" sourceRef="zuZhangJudgeGateway" targetRef="sendFailMessage" name="组长审核拒绝"><conditionExpression xsi:type="tFormalExpression">${var:equals(zuZhangCheckResult,"拒绝")}</conditionExpression></sequenceFlow><endEvent id="failEndLeave"/><sequenceFlow id="go_fail_end" sourceRef="sendFailMessage" targetRef="failEndLeave"/><sequenceFlow id="managerRefuse" sourceRef="managerJudgeGateway" targetRef="sendFailMessage" name="经理审核拒绝"><conditionExpression xsi:type="tFormalExpression">${var:equals(managerCheckResult,"拒绝")}</conditionExpression></sequenceFlow></process><bpmndi:BPMNDiagram id="BPMNDiagram_ask_for_leave"><bpmndi:BPMNPlane bpmnElement="ask_for_leave" id="BPMNPlane_ask_for_leave"><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-92e89192-ae6e-4c06-84ea-e3deb6d92402" bpmnElement="startLeave"><omgdc:Bounds x="-454.35" y="-60.0" width="30.0" height="30.0"/></bpmdi:BPMNShape><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-9ab478cf-067b-4984-a12e-0b14d28d6bb5" bpmnElement="leaveTask"><omgdc:Bounds x="-381.4296" y="-85.0" width="100.0" height="80.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-5e4ce6f7-76af-4972-91a1-ce776b2ca0c7" bpmnElement="flowStart"><omgdi:waypoint x="-424.35" y="-45.0"/><omgdi:waypoint x="-381.4296" y="-45.0"/></bpmdi:BPMNEdge><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-46866177-80d1-4db8-abd7-f0426459804a" bpmnElement="zuZhangTask"><omgdc:Bounds x="-234.44722" y="-85.0" width="100.0" height="80.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-c65d7d95-b9f8-44e1-89a3-0bf2a9e95738" bpmnElement="modeFlow"><omgdi:waypoint x="-281.4296" y="-45.0"/><omgdi:waypoint x="-234.44722" y="-45.0"/></bpmdi:BPMNEdge><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-53989386-6942-4e52-8f37-9076c52057a6" bpmnElement="zuZhangJudgeGateway"><omgdc:Bounds x="-78.09332" y="-65.0" width="40.0" height="40.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-4b34e417-f0c0-40ef-8bb6-7690e136d67f" bpmnElement="zuZhang_go_judge"><omgdi:waypoint x="-134.44722" y="-45.0"/><omgdi:waypoint x="-78.09332" y="-45.0"/></bpmdi:BPMNEdge><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-86d89ed3-dcb1-451c-b96c-ba3ef43baeba" bpmnElement="managerTask"><omgdc:Bounds x="26.607117" y="-85.0" width="100.0" height="80.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-c2ec3e89-62ab-4e90-af28-0cf551530ca0" bpmnElement="zuZhangPass"><omgdi:waypoint x="-38.093323" y="-45.0"/><omgdi:waypoint x="26.607117" y="-45.0"/></bpmdi:BPMNEdge><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-dd6c6e8f-e05d-4e50-9852-672656a76494" bpmnElement="endLeave"><omgdc:Bounds x="284.48773" y="-59.999996" width="30.0" height="30.0"/></bpmdi:BPMNShape><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-fa4fc0dc-9773-4944-9fde-d2741dc9386b" bpmnElement="managerJudgeGateway"><omgdc:Bounds x="164.3713" y="-65.0" width="40.0" height="40.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-4d924eb1-02a1-43d3-b884-d7a3e5918359" bpmnElement="manager_go_judge"><omgdi:waypoint x="126.60712" y="-45.0"/><omgdi:waypoint x="164.3713" y="-45.0"/></bpmdi:BPMNEdge><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-9918da8c-b7e5-4ec1-866b-dbfae2c6ea05" bpmnElement="managerPass"><omgdi:waypoint x="204.3713" y="-45.0"/><omgdi:waypoint x="284.48773" y="-44.999996"/></bpmdi:BPMNEdge><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-ccc89da3-1cf3-414b-8c39-3a90c8a42326" bpmnElement="sendFailMessage"><omgdc:Bounds x="-108.09332" y="41.54" width="100.0" height="80.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-98a4db5f-6ff9-48d0-a358-9470a03c8f1c" bpmnElement="zuZhangRefuse"><omgdi:waypoint x="-58.093323" y="-25.0"/><omgdi:waypoint x="-58.093323" y="41.54"/></bpmdi:BPMNEdge><bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-48b0f47e-8768-4275-b013-575039f2cd20" bpmnElement="failEndLeave"><omgdc:Bounds x="-199.44724" y="66.54" width="30.0" height="30.0"/></bpmdi:BPMNShape><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-08d8631a-3c62-486f-9a65-6f1d1a63c317" bpmnElement="go_fail_end"><omgdi:waypoint x="-108.09332" y="81.54"/><omgdi:waypoint x="-169.44724" y="81.54"/></bpmdi:BPMNEdge><bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-136a3062-a9f0-4c34-a275-0d6b51fc55d3" bpmnElement="managerRefuse"><omgdi:waypoint x="184.3713" y="-25.0"/><omgdi:waypoint x="184.3713" y="81.54"/><omgdi:waypoint x="-8.093322" y="81.53999"/></bpmdi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>
五、流程接口代码
AskForLeaveController.java
package com.test.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.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;
import java.util.Map;@RestController
public class AskForLeaveController {@AutowiredRuntimeService runtimeService;@AutowiredRepositoryService repositoryService;@AutowiredTaskService taskService;@AutowiredProcessEngine processEngine;/*** 查看流程进度图* @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_for_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();//提交给组长的时候,需要指定组长的idMap<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();// 提交给经理的时候,需要指定经理的idMap<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;}
}
LeaveFailService.java
对应流程图中的<serviceTask id="sendFailMessage" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="com.test.service.LeaveFailService"/>
package com.test.service;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;@Component
public class LeaveFailService implements JavaDelegate {@Overridepublic void execute(DelegateExecution delegateExecution) {System.out.println("审批不通过 " + delegateExecution.getCurrentActivityId());}
}
FlowableConfig.java,防止流程图出现中文乱码的配置类
package com.test.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> {@Overridepublic void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {springProcessEngineConfiguration.setActivityFontName("宋体");springProcessEngineConfiguration.setLabelFontName("宋体");springProcessEngineConfiguration.setAnnotationFontName("宋体");}
}
五、测试一下流程接口
1、开启一个请假流程,http://localhost:8080/askForLeave
查看流程进度图,参数是开启的流程的processId
http://localhost:8080/viewFlowChart?processId=ef67cd51-af01-11ef-8fdb-005056c00001,
可以看到,请假这个userTask用红色的框框起来了,说明当前流程走到了这一步
2、员工请假提交给组长审核,http://localhost:8080/submitToZuZhang
查看流程进度图
3、组长审核请假,http://localhost:8080/zuZhangApprove
查看流程进度
4、经理审核拒绝,http://localhost:8080/managerRefuse
调用LeaveFailService,控制台打印
流程结束
5、重新开启一个流程,重复1-4,试一试“经理审核通过”
相关文章:
SpringBoot集成Flowable
一、工作流介绍 1、概念 通过计算机对业务流程的自动化管理。工作流是建立在业务流程的基础上,一个软件的系统核心根本上还是系统的业务流程,工作流只是协助进行业务流程管理。 解决的是:在多个参与者之间按照某种预定义的规则自动进行传递文…...
五,[GXYCTF2019]Ping Ping Ping1
进入靶场,有提示 我们在url试着输入本地IP,返回了ping命令 既然要在url处传参,那就用postman,再输入ip127.0.0.1 & ls,试着列出目录内容 ok,好像是个脏话,它过滤了空格 试着穿越又看到了脏话࿰…...
Linux -初识 与基础指令1
博客主页:【夜泉_ly】 本文专栏:【Linux】 欢迎点赞👍收藏⭐关注❤️ 文章目录 📚 前言🖥️ 初识🔐 登录 root用户👥 两种用户➕ 添加用户🧑💻 登录 普通用户⚙️ 常见…...
单片机学习笔记 12. 定时/计数器_定时
更多单片机学习笔记:单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…...
采用片上光学相控阵的激光雷达
激光雷达基础知识 LIDAR 基于众所周知的 RADAR 原理雷达是20世纪初就存在的著名技术激光雷达使用光频率而不是无线电波 激光雷达和雷达 使用相控阵的激光雷达通过干涉来提高方向性 激光雷达的输出剖面是阵列因子和单天线远场的乘积。 N :天线数量 k :…...
LeetCode Hot100 11~20
目录 子串11. 滑动窗口最大值12. 最小覆盖子串 数组13. 最大子数组和14. 合并区间15. 翻转数组16. 除数字自身以外的乘积17. 缺失的第一个正数 矩阵18. 矩阵置零19. 螺旋矩阵20 旋转图像90度 子串 11. 滑动窗口最大值 本题使用deque来维护一个单调队列 注意删除元素和添加元素…...
泰州榉之乡全托机构探讨:自闭症孩子精细动作训练之法
当发现自闭症孩子精细动作落后时,家长们往往会感到担忧和困惑。那么,自闭症孩子精细动作落后该如何训练呢?今天,泰州榉之乡全托机构就来为大家详细解答。 榉之乡大龄自闭症托养机构在江苏、广东、江西等地都有分校,一直…...
Mybatis:CRUD数据操作之修改数据update
Mybatis基础环境准备请看:Mybatis基础环境准备 本篇讲解Mybati数据CRUD数据操作之修改数据 用户在该页面书写需要修改的数据,点击 提交 按钮,就会将数据库中对应的数据进行修改。注意一点,如果哪儿个输入框没有输入内容ÿ…...
模拟器快速上手,助力HarmonyOS应用/服务高效开发
文章目录 1 创建模拟器1)打开设备管理界面2)设置本地模拟器实例存储路径3)创建一个模拟器(1)选择模拟器设备(2)创建模拟器(3)启动模拟器(4)关闭模…...
ERROR in [eslint] Invalid Options ‘extensions‘ has been removed.
看着这个报错 感觉是版本不对引起的 ERROR in [eslint] Invalid Options: - Unknown options: extensions - extensions has been removed. ERROR in Error: Child compilation failed: [eslint] Invalid Options: - Unknown options: extensions - extensions has b…...
40分钟学 Go 语言高并发:GC原理与优化
GC原理与优化 一、GC基础知识概览 方面核心概念重要性优化目标GC算法三色标记法、并发GC⭐⭐⭐⭐⭐理解GC工作原理垃圾回收策略触发条件、回收步骤⭐⭐⭐⭐⭐掌握GC过程GC调优参数设置、性能监控⭐⭐⭐⭐优化GC效果内存管理内存分配、内存逃逸⭐⭐⭐⭐⭐减少内存压力 让我们…...
【UG\NX二次开发-Block UI】指定方位 VisibleManipulatorHandles 设置控制器手柄可见
特定于块属性 VisibleManipulatorHandles 值 Origin 0x1 原点 Ratate X 0x10 旋转 Ratate Y 0x20 Ratate Z 0x40 Translate X 0x2 平移 Translate Y 0x4 Translate Z 0…...
【Spring】聊聊@EventListener注解原理
1.一个Demo出发 在平时的开发中,其实编写同步线程代码是比较容易的,但是如何将一些操作和另外一些操作进行解除耦合,而事件方式 是一种很好的解耦合方式,比如当一个用户注销一个APP之后,需要发送一些短信 让他引流回来…...
Online Judge——【前端项目初始化】全局状态管理
状态管理:所有页面全局共享的变量,而不是局限在某一个页面中。 适合作为全局状态的数据:比如已登录用户信息。 目录 一、创建user.ts文件二、定义user模块三、获取静态变量四、修改状态变量 一、创建user.ts文件 我们要实现状态管理的话&am…...
微信小程序构建npm失败,没有找到可以构建的npm包
方法:打开终端输入 npm init -y npm install 或 yarn install我用 npm install 下载后并没有出现node_modules, 又用 yarn install 下载,成功了 下载好后,在project.config.json文件添加 "showShadowRootInWxmlPanel": true, …...
《数字图像处理基础》学习07-图像几何变换之最近邻插值法放大图像
目录 一,概念 二,题目及matlab实现 1,解题思路 2,matlab实现 1)matlab思路 2)完整代码 三,放大图像及matlab实现 一,概念 通过上一篇,我已经学习了使用最邻近插…...
科技为翼 助残向新 高德地图无障碍导航规划突破1.5亿次
今年12月03日是第33个国际残疾人日。在当下科技发展日新月异的时代,如何让残障人士共享科技红利、平等地参与社会生活,成为当前社会关注的热点。 中国有超过8500万残障人士,其中超过2400万为肢残人群,视力障碍残疾人数超过1700万…...
数据结构——有序二叉树的构建遍历查找
树节点 先定义树节点结构,代码如下: package tree;public class TreeNode {public int data;public TreeNode left;public TreeNode right;//数据的类型决定数据在内存中的存储形式,//这样可以接受本类型的数据public TreeNode(int data) {this.datada…...
React 状态管理:Redux 和 MobX 的对比与选择
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
CSDN设置成黑色背景(谷歌 Edge)
一.谷歌浏览器 浏览器地址输入:Chrome://flags搜索框输入:enable-force-dark将default 改成 enabled,点击重启浏览器 二.Edge浏览器 浏览器地址输入:edge://flags搜索里面输入Auto Dark Mode for Web Contents将default 改成 e…...
SAP Native SQL 的简单说明
Open SQL访问数据字典中声明的数据库表,不区分数据库类型,执行时会自动转换为对应的语句,且可以使用本地缓存。Native SQL使用特定于数据库的SQL语句,但是可以访问比Open SQL 更多的表,更多的操作,缺点也很明显&#x…...
shodan2-批量查找CVE-2019-0708漏洞
声明! 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&#…...
LabVIEW实现RS485通信
目录 1、RS485通信原理 2、硬件环境部署 3、串口通信函数 4、程序架构 5、前面板设计 6、程序框图设计 7、测试验证 本专栏以LabVIEW为开发平台,讲解物联网通信组网原理与开发方法,覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例,展示如何利用LabVIEW和常…...
【分页查询】.NET开源 ORM 框架 SqlSugar 系列
.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…...
【QT入门到晋级】QT项目打生产环境包--(Linux和window)
前言 使用QTcreator完成正常编译后,在构建目录中有可执行程序生成,如果直接把可执行程序拷贝到干净的生产环境上是无法运行成功的,使用ldd(查看程序依赖包)会发现缺失很多QT的特性包,以及将介绍国产Linux桌…...
Milvus×Florence:一文读懂如何构建多任务视觉模型
近两年来多任务学习(Multi-task learning)正取代传统的单任务学习(single-task learning),逐渐成为人工智能领域的主流研究方向。其原因在于,多任务学习可以让我们以最少的人力投入,获得尽可能多…...
深入理解异步编程:使用 `asyncio` 和 `aiohttp` 进行并发请求
深入理解异步编程:使用 asyncio 和 aiohttp 进行并发请求 1. 异步编程简介2. 代码结构概览3. 代码详解3.1 fetch 函数3.2 fetch_all 函数3.3 main 函数3.4 主程序 4. 性能分析5. 总结 在现代的Web开发中,性能优化是一个非常重要的课题。特别是在处理大量…...
C++之虚函数
对基类中的方法进行重写; 主要是通过继承机制 V 表实现; 虚函数的引入与不加入虚函数的主要区别在于 动态多态性。通过将 Entity 类的 GetName 函数声明为 virtual,可以实现 运行时多态,这意味着程序会根据对象的实际类型调用相应…...
buildroot 制作Linux嵌入式文件系统,并添加telnet 以及ssh
在开始配置前,我们需要了解SSH和Telnet的基本概念。SSH(Secure Shell)为加密的网络协议,用于在不安全的网络中执行命令并管理网络服务。相对于SSH,Telnet是一个老旧且非加密的协议,用于进行远程登录 sshd 服…...
(Linux 系统)进程控制
目录 一、进程创建 1、fork函数初识 二、进程终止 1、正常终止 2、异常终止 三、进程等待 1、进程等待必要性 2、进程等待的方法: 四、获取子进程status 1、基本概念 2、进程的阻塞等待方式 3、进程的非阻塞等待方式 五、进程程序替换 1、六种替换函数…...
Redis进行性能优化可以考虑的一些策略
选择合适的数据结构 根据实际的需求选择合适的数据结构,以高效地访问和存储多个属性。 比如如果你需要存储用户的多个属性,如用户名、邮箱等,使用哈希可以比使用多个字符串键值对更节省内存 避免大key/value 较大地key和value会占用更多的…...
win10系统部署RAGFLOW+Ollama教程
本篇主要基于linux服务器部署ragflowollama,其他操作系统稍有差异但是大体一样。 一、先决条件 CPU ≥ 4核; RAM ≥ 16 GB; 磁盘 ≥ 50 GB; Docker ≥ 24.0.0 & Docker Compose ≥ v2.26.1。 如果尚未在本地计算机ÿ…...
新型大语言模型的预训练与后训练范式,Meta的Llama 3.1语言模型
前言:大型语言模型(LLMs)的发展历程可以说是非常长,从早期的GPT模型一路走到了今天这些复杂的、公开权重的大型语言模型。最初,LLM的训练过程只关注预训练,但后来逐步扩展到了包括预训练和后训练在内的完整…...
【Spark源码分析】规则框架-草稿
规则批:规则集合序列,由名称、执行策略、规则列表组成。一个规则批里使用一个执行规则。 执行策略 FixedPointOnce 规则: #mermaid-svg-1cvqR4xkYpMuAs77 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px…...
力扣第 77 题 组合
题目描述 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按任意顺序返回答案。 示例 示例 1 输入: n 4, k 2输出: [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]示例 2 输入: n 1, k …...
postman中获取随机数、唯一ID、时间日期(包括当前日期增减)截取指定位数的字符等
在Postman中,您可以使用内置的动态变量和编写脚本的方式来获取随机数、唯一ID、时间日期以及截取指定位数的字符。以下是具体的操作方法: 一、postman中获取随机数、唯一ID、时间日期(包括当前日期增减)截取指定位数的字符等 获取…...
【汇编】逻辑指令
文章目录 一、逻辑运算指令(一)各逻辑运算指令格式及操作(1)逻辑非指令 NOT(2)逻辑与指令 AND(3)逻辑或指令 OR(4)异或指令 XOR(5)测试…...
LinkedList的了解
一、LinkedList的定义与类型 Java中的LinkedList类是一个双向链表(Doubly Linked List)。与单向链表(Singly Linked List)不同,双向链表中的每个节点不仅包含指向下一个节点的引用,还包含指向前一个节点的…...
2411mfc,修改按钮颜色
添加消息:ON_WM_CTLCOLOR() //在OnInitDialog()方法中添加{HWND hSatateWnd GetDlgItem(IDC_CHK)->GetSafeHwnd();SetWindowTheme(hSatateWnd, _T(""), _T(""));}头文件中: afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);HBRUSH O…...
NLP中的主题模型:LDA(Latent Dirichlet Allocation, 潜在狄利克雷分配)
探索自然语言处理中的主题模型:LDA与狄利克雷分布 主题模型是一种用于发现文档集合中潜在主题的概率生成模型。其中,LDA(Latent Dirichlet Allocation, 潜在狄利克雷分配)是最著名的主题模型之一。在 LDA 中,狄利克雷…...
javaweb Day11
Maven高级 1.分模块设计...
pnpm的menorepo项目配置eslint和prettier
1、使用eslint脚手架安装相关依赖并生成对应配置文件 pnpm dlx eslint/create-config 自动安装了以下几个插件 生成的配置文件如下所示,和csdn其他教程里面不一样,这是因为eslint升级成9.xx版本了 需要node版本20以上 eslint 9.x 升级或使用指南…...
从0开始linux(38)——线程(1)线程概念
欢迎来到博主专栏:从0开始linux 博主ID:代码小豪 文章目录 进程与线程线程概念线程的优点线程的独立数据 进程与线程 如果要理解线程,那么进程将会时绕不开的点。首先我们回顾一下我们之前在进程章节当中是如何描述进程的? 进程&…...
【Linux系统编程】:进程池(简易版)
目录 1.制作游戏菜单 2.对管道进行描述和组织 3.初始化管道 3.1子进程执行任务slaver() 3.2检查管道是否创建有误 4.父进程向管道写入(控制子进程执行任务) 5.清理资源 修改初始化管道代码 6.完整代码: 1.制作游戏菜单 我们利用管道…...
uniapp实现小程序的版本更新
参考官方文档:uni.getUpdateManager() | uni-app官网 uni.getUpdateManager()是uniapp框架提供的一个API,用于管理小程序的版本更新。这个API返回一个全局唯一的版本更新管理器对象,该对象可以用于检测新版本、下载新版本以及提示用户重启应…...
嵌入式QT学习第4天:Qt 信号与槽
Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本章思维导图如下: 不使用 Qt Designer 的方式进行开发,用代码绘界面,可以锻炼我们的布局能力,和代码逻辑能力&#x…...
`asyncio.wait` 和 `asyncio.gather` 的区别
asyncio.wait 和 asyncio.gather 的区别 1. asyncio.wait2. asyncio.gather主要区别总结 在Python的异步编程中,asyncio.wait 和 asyncio.gather 都是用于等待多个异步任务完成的工具,但它们在功能和使用方式上有一些关键的区别。本文将详细解释这两个函…...
【JavaEE】多线程(2)
一、线程安全 1.1 线程安全的概念 线程是随机调度执行的,如果多线程环境下的程序运行的结果符合我们预期则说明线程安全,反之,如果遇到其他结果甚至引起了bug则说明线程不安全 1.2 经典例子与解释 下面举一个经典的线程不安全的例子&…...
虚拟地址空间与物理内存(Linux系统)
个人主页:敲上瘾-CSDN博客 个人专栏:Linux学习、游戏、数据结构、c语言基础、c学习、算法 目录 问题引入 一、什么是虚拟内存 二、虚拟内存的描述与组织 三、页表的优势 四、虚拟内存区域划分 问题引入 为引入今天的话题,我们先来看下面…...
iOS Arkit机器学习相关
最近在搞追踪运动物体,然后Arkit识别3d模型和图片,静止状态还不错,运动时候效果还是有点差,所以搞了下苹果的CoreML,苹果官网也有一些训练好的模型 苹果模型列表,自己参考或者使用,提供一个数据…...