springBoot 3.X整合camunda

camunDa

camunDa 是2013年从Activiti5 中分离出来的一个新的工作流引擎。Camunda 官方提供了 Camunda Platform、Camunda Modeler,其中 Camunda Platform 以 Camunda engine 为基础为用户提供可视化界面,Camunda Modeler 是流程文件建模平台,在 Camunda Modeler 创建的流程文件可以 deploy 到 Camunda Platform 并进行管理。另外三方服务可通过 Camunda 官方提供的 rest 或者 java api 来访问 Camunda engine,操作的结果也可以在 Camunda Platform 查看和管理。

camunDa环境搭建

准备条件:JDK17,Maven版本3.8.6
具体代码可以通过CamunDa官方提供的框架生成器自动生成基础环境代码
CamunDa基础项目生成链接:https://start.camunda.com/
生成代码之后,可以得到具体的使用DEMO了,但是不一定能适合在实际工作中,所以部分代码是需要进行调整的
博主这边也贴上自己搭建的具体步骤(使用官方搭建的目的是快速使用,具体使用那种搭建方式,自行把握吧!)

POM依赖(最外层POM文件依赖数据)

<?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"><!--<parent><artifactId>base-platform</artifactId><groupId>com.secondcloud</groupId><version>1.0.0</version></parent>--><parent><groupId>com.secondcloud.dependency</groupId><artifactId>dependency-parent</artifactId><version>1.0.0-SNAPSHOT</version></parent><groupId>com.secondcloud</groupId><artifactId>new-workflow-engine</artifactId><version>1.0.0</version><modelVersion>4.0.0</modelVersion><packaging>pom</packaging><description>工作流服务</description><modules><module>new-workflow-engine-client</module><module>new-workflow-engine-server</module></modules><profiles><profile><id>dev</id><properties><!--当前环境--><profile.name>dev</profile.name><!--私有镜像仓库--><docker.registry>******:5000</docker.registry><!--Nacos配置中心地址;服务发现地址--><nacos.server-addr>******:8848</nacos.server-addr><!--Nacos配置中心命名空间,用于支持多环境.这里必须使用ID,不能使用名称,默认为空--><nacos.namespace>dev</nacos.namespace><nacos.username>nacos</nacos.username><nacos.password>nacos</nacos.password></properties></profile><!-- 测试 --><!--<profile><id>test</id><activation><activeByDefault>true</activeByDefault></activation><properties><profile.name>test</profile.name><docker.registry>******:5000</docker.registry><nacos.server-addr>******:8848</nacos.server-addr><nacos.namespace>test</nacos.namespace><nacos.username>nacos</nacos.username><nacos.password>nacos</nacos.password></properties></profile>--></profiles>
</project>

server服务pom文件依赖

<?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"><parent><artifactId>new-workflow-engine</artifactId><groupId>com.secondcloud</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.secondcloud</groupId><artifactId>new-workflow-engine-server</artifactId><version>1.0.0</version><packaging>jar</packaging><dependencies><!-- 引入基础依赖 server通用依赖引入-,这些依赖可以不是CamunDa的主要依赖,是实际项目中用到的-><dependency><groupId>com.secondcloud.dependency</groupId><artifactId>dependency-server</artifactId><version>1.0.0-SNAPSHOT</version></dependency><dependency><groupId>com.baidu.contact-center</groupId><artifactId>cf-auth-api-token</artifactId><version>5.1.5</version></dependency><dependency><groupId>com.secondcloud.client</groupId><artifactId>new-workflow-engine-client</artifactId><version>1.0.0-SNAPSHOT</version><scope>compile</scope></dependency><!-- camunda 依赖-><dependency><groupId>org.camunda.bpm.springboot</groupId><artifactId>camunda-bpm-spring-boot-starter</artifactId><version>7.19.0</version><exclusions><exclusion><artifactId>spring-boot-starter-logging</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><!-- Rest服务接口 --><dependency><groupId>org.camunda.bpm.springboot</groupId><artifactId>camunda-bpm-spring-boot-starter-rest</artifactId><version>7.19.0</version><exclusions><exclusion><artifactId>spring-boot-starter-tomcat</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><!-- Spin (XML & JSON) --><dependency><groupId>org.camunda.bpm</groupId><artifactId>camunda-engine-plugin-spin</artifactId><version>7.19.0</version></dependency><dependency><groupId>org.camunda.spin</groupId><artifactId>camunda-spin-dataformat-all</artifactId><version>1.19.5</version></dependency><dependency><groupId>org.camunda.bpm.springboot</groupId><artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId><version>7.19.0</version></dependency></dependencies><build><!-- 打包名称:默认带版本号 --><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!-- 打包后,将jar复制到指定目录 --><!--<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-antrun-plugin</artifactId></plugin>--></plugins><resources><resource><directory>src/main/resources</directory><includes><include>**/**.*</include></includes><filtering>true</filtering></resource></resources></build>
</project>

client依赖POM

<?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"><parent><groupId>com.secondcloud.dependency</groupId><artifactId>dependency-parent</artifactId><version>1.0.0-SNAPSHOT</version><relativePath></relativePath></parent><groupId>com.secondcloud.client</groupId><artifactId>new-workflow-engine-client</artifactId><version>1.0.0-SNAPSHOT</version><modelVersion>4.0.0</modelVersion><packaging>jar</packaging><dependencies><!-- 引入基础依赖 dependency-client模块中已引入通用jar--><dependency><groupId>com.secondcloud.dependency</groupId><artifactId>dependency-client</artifactId><version>1.0.0-SNAPSHOT</version></dependency></dependencies></project>

这依赖几乎是空的,导入的唯一一个依赖是博主工作中具体使用到的依赖项目,大家是没法导入的,可以去除
项目目录结构也贴一下
项目结构

然后就需要去更改一下我们的数据库链接设置了
数据链接默认使用的是H2,我们需要改为我们自己对应的数据库和驱动,连接上数据库之后,启动项目就会自动生成工作流需要使用的表结构了

# 数据源
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: org.postgresql.Driverusername: rootpassword: 密码url: jdbc:postgresql://pg10:5432/workflow-managemain:allow-circular-references: true
urlHost: "https://test.jxvrgvtech.cn"

其次就是配置CamunDa的账户和密码

camunda.bpm.admin-user:id: dempassword: demo

相同的都是写在我们的yml文件里面即可
这样我们的项目环境就算是搭建完成了

CamunDa流程图建立

项目启动完成之后就会生成对应的表结构,我们可以在数据库中看到,如果没有生成,可能是数据库什么的配置有问题,可以通过官方提供的快速搭建方法再试一下,博主的项目因为涉及隐私较多,部分代码可能缺失了。
在这里插入图片描述
这些ACT开头的就是自动生成的表结构了
我们这个时候就要进行画流程图了,Camunda他是不自带流程图工具,是需要我们另外下载的,下载:https://camunda.com/download/modeler/
下载完成之后,打开页面是这样的
在这里插入图片描述
博主这边是使用bpmn文件的,不是挂载到云端的,所以选择第二个本地的形式画图
在这里插入图片描述
这是博主画的一个比较符合现实逻辑的一个请假流程示意图,下方也有相对应的说明,其中,在这里插入图片描述
这个带人头的方框是用户任务,也就是需要我们人为去手动触发,审核的,不不是系统自动处理的,这个博主就不再多说这个具体使用方法了
但是这个里面有一些配置,需要注意一下,不然我们的流程图是没法对应上我们的业务数据的
图1
图上已经标明了,具体的参数大家可以自行定义,其中的${starter}是从我们第一个启动流程里面获取到的,至于启动流程里面这个具体指,后面我们会再代码中具体讲到
在这里插入图片描述
在这里插入图片描述
这是流程发起之后,通过图1,的请假天数进行判断需要走到那个节点的,中间这个带X的图形是我们的一个排他网关,相当于IF语句,通过我们的EL表达式,进行流程流转
在这里插入图片描述
这个里面的leaders是我们从代码中进行添加的处理人的数据,可进行动态变更
在这里插入图片描述
这里是处理人处理的意见数据
在这里插入图片描述
最后就是流程结束了,我这边也附上流程图,大家可以复制下来,看具体数据

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1rrprgw" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.24.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.21.0"><bpmn:process id="Process_exclusive_gateway" name="排他网关" isExecutable="true" camunda:historyTimeToLive="180"><bpmn:startEvent id="StartEvent_1" name="开始" camunda:initiator="starter"><bpmn:outgoing>Flow_1pgd9ua</bpmn:outgoing></bpmn:startEvent><bpmn:sequenceFlow id="Flow_1pgd9ua" sourceRef="StartEvent_1" targetRef="Activity_1kmi54i" /><bpmn:userTask id="Activity_1kmi54i" name="员工请假" camunda:assignee="${starter}"><bpmn:extensionElements><camunda:formData><camunda:formField id="reason" label="请假理由" type="string" /><camunda:formField id="leaveDays" label="请假天数" type="long" defaultValue="1" /></camunda:formData></bpmn:extensionElements><bpmn:incoming>Flow_1pgd9ua</bpmn:incoming><bpmn:outgoing>Flow_05ygtrp</bpmn:outgoing></bpmn:userTask><bpmn:exclusiveGateway id="Gateway_1tbcq0a"><bpmn:incoming>Flow_05ygtrp</bpmn:incoming><bpmn:outgoing>Flow_09sdkiu</bpmn:outgoing><bpmn:outgoing>Flow_138ijpi</bpmn:outgoing><bpmn:outgoing>Flow_0gdtha1</bpmn:outgoing></bpmn:exclusiveGateway><bpmn:sequenceFlow id="Flow_05ygtrp" sourceRef="Activity_1kmi54i" targetRef="Gateway_1tbcq0a" /><bpmn:sequenceFlow id="Flow_09sdkiu" name="小于等于三天" sourceRef="Gateway_1tbcq0a" targetRef="Activity_1hvlj71"><bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">${leaveDays&lt;=3}</bpmn:conditionExpression></bpmn:sequenceFlow><bpmn:userTask id="Activity_1hvlj71" name="直接领导审批" camunda:assignee="zhangsan"><bpmn:extensionElements><camunda:formData><camunda:formField id="comment" label="评论" type="string" defaultValue="同意" /></camunda:formData></bpmn:extensionElements><bpmn:incoming>Flow_09sdkiu</bpmn:incoming><bpmn:outgoing>Flow_0yqeod6</bpmn:outgoing></bpmn:userTask><bpmn:intermediateThrowEvent id="Event_1pl4d8u"><bpmn:incoming>Flow_0yqeod6</bpmn:incoming><bpmn:incoming>Flow_08juds0</bpmn:incoming><bpmn:incoming>Flow_1ouaja5</bpmn:incoming></bpmn:intermediateThrowEvent><bpmn:sequenceFlow id="Flow_0yqeod6" sourceRef="Activity_1hvlj71" targetRef="Event_1pl4d8u" /><bpmn:sequenceFlow id="Flow_138ijpi" name="大于三天小于等于五天" sourceRef="Gateway_1tbcq0a" targetRef="Activity_0jv1xqx"><bpmn:extensionElements><camunda:executionListener delegateExpression="${addLeaders}" event="take" /></bpmn:extensionElements><bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">${leaveDays&gt;3 &amp;&amp; leaveDays&lt;=5}</bpmn:conditionExpression></bpmn:sequenceFlow><bpmn:userTask id="Activity_0jv1xqx" name="直接领导和经理审批" camunda:assignee="${leader}"><bpmn:extensionElements><camunda:formData><camunda:formField id="comment" label="评论" type="string" defaultValue="同意" /></camunda:formData></bpmn:extensionElements><bpmn:incoming>Flow_138ijpi</bpmn:incoming><bpmn:outgoing>Flow_08juds0</bpmn:outgoing><bpmn:multiInstanceLoopCharacteristics isSequential="true" camunda:collection="${leaders}" camunda:elementVariable="leader" /></bpmn:userTask><bpmn:sequenceFlow id="Flow_08juds0" sourceRef="Activity_0jv1xqx" targetRef="Event_1pl4d8u" /><bpmn:sequenceFlow id="Flow_0gdtha1" name="大于五天" sourceRef="Gateway_1tbcq0a" targetRef="Activity_19axysr"><bpmn:extensionElements><camunda:executionListener delegateExpression="${addLeaders}" event="take" /></bpmn:extensionElements><bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">${leaveDays&gt;5}</bpmn:conditionExpression></bpmn:sequenceFlow><bpmn:userTask id="Activity_19axysr" name="直接领导、经理和董事长审批" camunda:assignee="${leader}"><bpmn:extensionElements><camunda:formData><camunda:formField id="comment" label="评论" type="string" defaultValue="同意" /></camunda:formData></bpmn:extensionElements><bpmn:incoming>Flow_0gdtha1</bpmn:incoming><bpmn:outgoing>Flow_1ouaja5</bpmn:outgoing><bpmn:multiInstanceLoopCharacteristics isSequential="true" camunda:collection="${leaders}" camunda:elementVariable="leader" /></bpmn:userTask><bpmn:sequenceFlow id="Flow_1ouaja5" sourceRef="Activity_19axysr" targetRef="Event_1pl4d8u" /></bpmn:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_exclusive_gateway"><bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1"><dc:Bounds x="152" y="232" width="36" height="36" /><bpmndi:BPMNLabel><dc:Bounds x="159" y="275" width="22" height="14" /></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_0e8erbr_di" bpmnElement="Activity_1kmi54i"><dc:Bounds x="340" y="210" width="100" height="80" /><bpmndi:BPMNLabel /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Gateway_1tbcq0a_di" bpmnElement="Gateway_1tbcq0a" isMarkerVisible="true"><dc:Bounds x="515" y="225" width="50" height="50" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_0p55xr1_di" bpmnElement="Activity_1hvlj71"><dc:Bounds x="1010" y="57" width="100" height="80" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Event_1pl4d8u_di" bpmnElement="Event_1pl4d8u"><dc:Bounds x="1042" y="232" width="36" height="36" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_1tadrud_di" bpmnElement="Activity_0jv1xqx"><dc:Bounds x="790" y="210" width="100" height="80" /><bpmndi:BPMNLabel /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_0ourst1_di" bpmnElement="Activity_19axysr"><dc:Bounds x="1010" y="390" width="100" height="80" /></bpmndi:BPMNShape><bpmndi:BPMNEdge id="Flow_1pgd9ua_di" bpmnElement="Flow_1pgd9ua"><di:waypoint x="188" y="250" /><di:waypoint x="340" y="250" /><bpmndi:BPMNLabel><dc:Bounds x="493" y="191" width="22" height="14" /></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_05ygtrp_di" bpmnElement="Flow_05ygtrp"><di:waypoint x="440" y="250" /><di:waypoint x="515" y="250" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_09sdkiu_di" bpmnElement="Flow_09sdkiu"><di:waypoint x="540" y="225" /><di:waypoint x="540" y="97" /><di:waypoint x="1010" y="97" /><bpmndi:BPMNLabel><dc:Bounds x="522" y="161" width="66" height="14" /></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_0yqeod6_di" bpmnElement="Flow_0yqeod6"><di:waypoint x="1060" y="137" /><di:waypoint x="1060" y="232" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_138ijpi_di" bpmnElement="Flow_138ijpi"><di:waypoint x="565" y="250" /><di:waypoint x="790" y="250" /><bpmndi:BPMNLabel><dc:Bounds x="645" y="232" width="77" height="27" /></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_08juds0_di" bpmnElement="Flow_08juds0"><di:waypoint x="890" y="250" /><di:waypoint x="1042" y="250" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_0gdtha1_di" bpmnElement="Flow_0gdtha1"><di:waypoint x="540" y="275" /><di:waypoint x="540" y="430" /><di:waypoint x="1010" y="430" /><bpmndi:BPMNLabel><dc:Bounds x="533" y="350" width="44" height="14" /></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_1ouaja5_di" bpmnElement="Flow_1ouaja5"><di:waypoint x="1060" y="390" /><di:waypoint x="1060" y="268" /></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</bpmn:definitions>

这个代码就是流程图生成出来的bpmn文件了,其中注意一下,使用官方生成的项目框架生成的bpmn文件中会缺失一个数据,我这边进行截图标识一下,需要我们手动添加在这里插入图片描述
如果不添加这个,启动时会报错的,到这了,流程图就算是画好了,我们再项目中建立一个文件加,专门存放流程图即可
在这里插入图片描述
启动成功之后,我们可以通过访问项目进去查看项目流程数量和在执行的数量等信息
访问地址的端口是我们再YML文件中配置的,大家自行变更
http://127.0.0.1:5003/camunda/app/cockpit/default/#/login
在这里插入图片描述
在这里插入图片描述

代码调用

到这里之后,就可以在项目中通过具体案例去使用工作流了
案例如下

package com.secondcloud.industry.server.controller;import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.Comment;
import org.camunda.bpm.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.*;@RestController
public class Demo {@Autowiredprivate IdentityService identityService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;/*** 创建流程实例* processDefKey:这里首先需要传入的是流程定义模板的key,前提是我们之前已经部署了相应的流程模板。(Process_0bhiqm1)* businessKey:这是流程实例的业务标识Key,需要用该字段与我们的业务单据进行绑定。* initiator:启动流程实例时的操作人,这里可以理解为制单人,或者是送审人,但是需要注意,在实际应用场景中,我们的制单人不一定就是单据送审人。*/@GetMapping("/startProcessInstanceByDefKey")public String startProcessInstanceByDefKey(@RequestParam(value = "processDefKey") String processDefKey,@RequestParam(value = "business") String business,@RequestParam(value = "applicantName") String applicantName) {HashMap<String, Object> variable = new HashMap<>();//流程启动初始化数据variable.put("initiator", applicantName);variable.put("isFree", true);identityService.setAuthenticatedUserId(applicantName); //ACT_HI_PROCINST.START_USER_ID字段的赋值-开始节点人ProcessInstance instance = runtimeService.startProcessInstanceByKey(processDefKey, business, variable);
//        PROCINST procinst = procinst = new PROCINST(instance.getProcessDefinitionId(), instance.getProcessInstanceId(), instance.getBusinessKey(), instance.isSuspended(), instance.isEnded());return "流程创建成功";}/*** 提交申请* leaveDays:模板中定义的数据变量。* business:这是流程实例的业务标识Key,需要用该字段与我们的业务单据进行绑定。* applicantName:单据送审人,也就是申请人*/@GetMapping("/submitApplication")public String submitApplication(@RequestParam(value = "leaveDays") Long leaveDays,@RequestParam(value = "business") String business,@RequestParam(value = "applicantName") String applicantName) {String resultString = "";Task task = queryTaskByBusinessKey(business, applicantName);if (ObjectUtils.isNull(task)) {resultString = "没有查询到对应的单据流程";} else if (!task.getAssignee().equalsIgnoreCase(applicantName)) {resultString = "没有审核权限!";} else {//设置审核人HashMap<String, Object> map = new HashMap<>();map.put("reason", "请假");map.put("leaveDays", leaveDays);List<String> leaders = new ArrayList<>();if (leaveDays > 3 && leaveDays <= 5) {leaders.add("zhangsan");leaders.add("lisi");} else if (leaveDays > 5) {leaders.add("zhangsan");leaders.add("lisi");leaders.add("wangwu");}map.put("leader", leaders);String taskId = task.getId();taskService.setVariables(taskId, map);taskService.complete(taskId);resultString = "请假成功!";}return resultString;}/*** 审核操作** @param businessKey 业务id* @param initiator   流程处理人* @param comment     处理意见*/@GetMapping("/submitProcessInstance")public String submitProcessInstance(@RequestParam(value = "businessKey") String businessKey,@RequestParam(value = "initiator") String initiator,@RequestParam(value = "comment") String comment) {String resultString = "";Task task = queryTaskByBusinessKey(businessKey, initiator);if (ObjectUtils.isNull(task)) {resultString = "没有查询到对应的单据流程";} else if (!task.getAssignee().equalsIgnoreCase(initiator)) {resultString = "没有审核权限!";} else {if (task.getAssignee().equals("zhangsan")) {Map<String, Object> variables = taskService.getVariables(task.getId());System.out.println("variables" + variables.toString());String flag = "true";HashMap<String, Object> map = new HashMap<>();map.put("flag", flag);map.put("comment", comment);taskService.createComment(task.getId(), task.getProcessInstanceId(), "审核原因:" + comment);taskService.complete(task.getId(), map);return "zhangsan" + (flag.equals("true") ? "同意" : "不同意");} else if (task.getAssignee().equals("lisi")) {String flag = "true";HashMap<String, Object> map = new HashMap<>();map.put("flag", flag);map.put("comment", comment);taskService.createComment(task.getId(), task.getProcessInstanceId(), "审核原因:" + comment);taskService.complete(task.getId(), map);return "lisi" + (flag.equals("true") ? "同意" : "不同意");} else if (task.getAssignee().equals("wangwu")) {String flag = "true";HashMap<String, Object> map = new HashMap<>();map.put("flag", flag);map.put("comment", comment);taskService.createComment(task.getId(), task.getProcessInstanceId(), "审核原因:" + comment);taskService.complete(task.getId(), map);return "wangwu" + (flag.equals("true") ? "同意" : "不同意");}}return resultString;}/*** 根据业务标识代码获取当前节点** @param businessKey 业务id* @param initiator   流程处理人或者流程制作人*/private Task queryTaskByBusinessKey(String businessKey, String initiator) {Task task = null;ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();if (ObjectUtils.isNull(instance)) return null;List<Task> list = taskService.createTaskQuery().processInstanceId(instance.getProcessInstanceId()).active() //正在运行时的节点?.list();if (list.size() == 1) task = list.get(0);else {task = taskService.createTaskQuery().processInstanceId(instance.getProcessInstanceId()).active().taskAssignee(initiator).singleResult();}return task;}@Autowiredprivate HistoryService historyService;/*** 查询我创建的流程** @param userId 创建流程人的用户ID* @return*/@GetMapping("/queryMySalaryProcess/{userId}")public List<String> queryMySalaryProcess(@PathVariable(value = "userId") String userId) {/* 迭代【可添加更多种条件查询】:时间范围、内置分页查询等! */List<HistoricProcessInstance> list = list = historyService.createHistoricProcessInstanceQuery()//通过制单人来查询流程中的数据.startedBy(userId).list();if (CollectionUtils.isEmpty(list)) {return null;}ArrayList<String> businessKeyList = new ArrayList<>();list.forEach(procInst -> {businessKeyList.add(procInst.getBusinessKey());});return businessKeyList;}/*** 查询已办/未办 单据** @param userId 用户id* @param type   数据类型*/@GetMapping("/queryMyTodoTask")public List queryMyTodoTask(@RequestParam(value = "userId") String userId,@RequestParam(value = "type") String type) {ArrayList<Object> businessList = new ArrayList<>();//查询代办if (type.equalsIgnoreCase("0")) {List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).active()
//                    .listPage() 【可分页查询】.list();if (CollectionUtils.isEmpty(tasks)) {return null;}tasks.forEach(task -> {//为查询到相关单据String businessKey = historyService.createHistoricProcessInstanceQuery()
//                        .orderByProcessInstanceStartTime() 【可添加时间查询范围】
//                        .orderByProcessInstanceEndTime().processInstanceId(task.getProcessInstanceId()).singleResult().getBusinessKey();businessList.add(businessKey);});}//查询已办else if (type.equalsIgnoreCase("1")) {List<HistoricTaskInstance> completedTaskList = historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).finished().taskDeleteReason("completed").list();if (CollectionUtils.isEmpty(completedTaskList)) {//为查询到相关单据return null;}completedTaskList.forEach(taskOld -> {String businessKey = historyService.createHistoricProcessInstanceQuery()
//                    .orderByProcessInstanceStartTime()  【可添加时间查询范围】
//                    .orderByProcessInstanceEndTime().processInstanceId(taskOld.getProcessInstanceId()).singleResult().getBusinessKey();businessList.add(businessKey);});}return businessList;}/*** 驳回操作*/@GetMapping("/turnTask")public String turnTask(@RequestParam(value = "userId") String userId,@RequestParam(value = "businessKey") String businessKey,@RequestParam(value = "type") String type,@RequestParam(value = "comment") String comment) {Task task = queryTaskByBusinessKey(businessKey, userId);if (ObjectUtils.isNull(task)) {String result = new String("未查询到对应的审核单据!");return new String("未查询到对应的审核单据!");}if (!task.getAssignee().equalsIgnoreCase(userId)) {return new String("没用审核权限!");}//获取所有已办节点List<HistoricActivityInstance> userTaskList = historyService.createHistoricActivityInstanceQuery().processInstanceId(task.getProcessInstanceId())//用户类型节点.activityType("userTask").finished() //已经完成的节点.orderByHistoricActivityInstanceEndTime().asc().list();//流程实例的活动实例树/* ActivityInstance tree = runtimeService.getActivityInstance(task.getProcessInstanceId()); */if (userTaskList == null || CollectionUtils.isEmpty(userTaskList)) {return new String("当前任务无法驳回!");}switch (type) {//驳回第一任制单人case "1": {// 1.驳回提交已经无  ok2.驳回第一任还是驳回的上一任  3.会签节点上的驳回操作出现只结束了当前任务的驳回!if (userTaskList.size() < 2) {return new String("第一个用户节点无法驳回!");}HistoricActivityInstance historicActivityInstance = userTaskList.get(0);String toActId = historicActivityInstance.getActivityId();String assignee = historicActivityInstance.getAssignee();//设置流程可变参数HashMap<String, Object> taskVariable = new HashMap<>();taskVariable.put("assignee", assignee);//流程审核+驳回//任务流程创建了提交模板Comment 但是没有提交 taskService.complete(taskId)。--所有节点也不会提交到后台去taskService.createComment(task.getId(), task.getProcessInstanceId(), "驳回原因:" + comment);//任务流程实例修改位置runtimeService.createProcessInstanceModification(task.getProcessInstanceId())
//                        .cancelActivityInstance(getInstanceIdForActivity(tree,task.getTaskDefinitionKey())) //关闭当前节点相关的任务.cancelAllForActivity(task.getTaskDefinitionKey())//暂时不清楚该内容提交到何处!.setAnnotation("进行了驳回到第一任务节点操作!")//启动目标活动节点.startBeforeActivity(toActId).setVariables(taskVariable).execute();return new String("驳回到制单人成功!");}//驳回上一任case "2": {//判断当前节点是否为第一个节点HistoricActivityInstance historicActivityInstance = userTaskList.get(0);String activityId = historicActivityInstance.getActivityId();if (activityId.equals(task.getTaskDefinitionKey())) {return new String("第一节点无法驳回!");}//获取上一个节点Map<String, String> lastNode = getLastNode(userTaskList, task.getTaskDefinitionKey());if (ObjectUtils.isNull(lastNode)) {return new String("退回节点异常!");}String toActId = lastNode.get("toActId");String assignee = lastNode.get("assignee");//设置流程中的可变参数HashMap<String, Object> taskVariable = new HashMap<>(2);taskVariable.put("leader", assignee);//进行驳回操作taskService.createComment(task.getId(), task.getProcessInstanceId(), "驳回:" + comment);runtimeService.createProcessInstanceModification(task.getProcessInstanceId())//.cancelActivityInstance(getInstanceIdForActivity(tree,task.getTaskDefinitionKey())) //关闭相关任务!(当前节点就会被删除。但是之前审核过的节点还是会存在!)//该方式关闭所有activityId相同的activity活动都会被取消暂停(会签节点).cancelAllForActivity(task.getTaskDefinitionKey()).setAnnotation("进行驳回到上一任务节点操作!")//启动目标活动节点.startBeforeActivity(toActId)//流程可变参数赋值.setVariables(taskVariable).execute();return new String("驳回上一任成功!");}//驳回任一任case "3": {//}default: {}}return null;}private Map<String, String> getLastNode(List<HistoricActivityInstance> resultList, String currentActivityId) {HashMap<String, String> backNode = new HashMap<>();//新建一个有序不重复集合LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();for (HistoricActivityInstance his : resultList) {linkedHashMap.put(his.getActivityId(), his.getAssignee());}int originSize = resultList.size();//判断历史节点中是否已经存在过当前节点boolean flag = false;for (Map.Entry entry : linkedHashMap.entrySet()) {if (currentActivityId.equalsIgnoreCase((String) entry.getKey())) {flag = true;break;}}//当前节点不在历史节点里面,最后一个节点是完成节点if (!flag) {HistoricActivityInstance historicActivityInstance = resultList.get(originSize - 1);backNode.put("toActId", historicActivityInstance.getActivityId());backNode.put("assignee", historicActivityInstance.getAssignee());return backNode;//当前节点在历史节点中(已经退回过)} else {ListIterator<Map.Entry<String, String>> li = new ArrayList<>(linkedHashMap.entrySet()).listIterator();while (li.hasNext()) {Map.Entry<String, String> entry = li.next();if (currentActivityId.equalsIgnoreCase(entry.getKey())) {//光标上一到当前节点li.previous();//当前相同节点的上一节点Map.Entry<String, String> previous = li.previous();backNode.put("toActId", previous.getKey());backNode.put("assignee", previous.getValue());return backNode;}}}return null;}//审核日志查询/*** [注:日志顺序  1开始时间 相同顺延 2排列结束时间]* activityType:节点类型 null就不显示* taskId:taskId相同的为会签节点* state:completed审核完成  deleted驳回  null待审核* */@GetMapping("/queryProcessLog/{businessKey}")public List queryProcessLog(@PathVariable(value = "businessKey") String businessKey) {String processInstanceId = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult().getRootProcessInstanceId();List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime() //这里开始时间相同的可以,只能前端在根据结束时间继续排序.asc().list();List<Map<String,Object>> result=new ArrayList<>(list.size());System.out.println(list.size());for (HistoricActivityInstance historicActivityInstance : list) {Map<String,Object> map=new HashMap<>();String taskId = historicActivityInstance.getTaskId();List<Comment> taskComments = taskService.getTaskComments(taskId);System.out.println("taskId = " + taskId);System.out.println(taskComments.size());map.put("activityName",historicActivityInstance.getActivityName());System.out.println("historicActivityInstance.getActivityType() = " + historicActivityInstance.getActivityType());map.put("activityType",matching(historicActivityInstance.getActivityType()));map.put("assignee",historicActivityInstance.getAssignee()==null?"无":historicActivityInstance.getAssignee());map.put("taskId",historicActivityInstance.getTaskId());map.put("act_Id",historicActivityInstance.getActivityId());/*加入activity状态字段*/if (taskId != null) {map.put("state",historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult().getDeleteReason());}Date startTime = historicActivityInstance.getStartTime();if (ObjectUtils.isNull(startTime)){map.put("starTime","");}else{map.put("startTime", DateFormatUtils.format(historicActivityInstance.getStartTime(),"yyyy-MM-dd HH:mm:ss") );}Date endTime = historicActivityInstance.getEndTime();if (ObjectUtils.isNull(endTime)){map.put("endTime","");map.put("costTime","");}else {map.put("endTime",DateFormatUtils.format(historicActivityInstance.getEndTime(),"yyyy-MM-dd HH:mm:ss"));map.put("costTime",getDatePoor(historicActivityInstance.getEndTime(),historicActivityInstance.getStartTime()));}if (taskComments.size()>0){map.put("message",taskComments.get(0).getFullMessage());}else {map.put("message","无");}result.add(map);}System.out.println("result = " + result);return result;}/** 时间差计算 */public  String getDatePoor(Date endDate, Date nowDate) {long nd = 1000 * 24 * 60 * 60;long nh = 1000 * 60 * 60;long nm = 1000 * 60;long ns = 1000;// 获得两个时间的毫秒时间差异long diff = endDate.getTime() - nowDate.getTime();// 计算差多少天long day = diff / nd;// 计算差多少小时long hour = diff % nd / nh;// 计算差多少分钟long min = diff % nd % nh / nm;// 计算差多少秒//输出结果long sec = diff % nd % nh % nm / ns;return day + "天" + hour + "小时" + min + "分钟"+ sec + "秒";}/** 日志log类型替换 */private String matching(String ActivityType){String value="";switch (ActivityType){case "startEvent":value="流程开始";break;case "userTask":value="用户处理";break;case "noneEndEvent":value="流程结束";break;default:value="未知节点";break;}return value;}}

上方代码是一个完整的流程,从流程发起到流程结束,审核通过、驳回,查询我创建的流程和我的代办已办和审核日志功能,
其中我们要注意的是,流程的创建者并不一定是流程的申请者(大多数情况下是一个,但是存在不是一个的情况),代码中也有较多的代码注释,具体的使用,大家仔细阅读即可理解掌握。

项目源码已经附上,有问题可评论留言!

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

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

相关文章

虚拟换装的一个项目:IMAGDressing-v1

虚拟换装的一个项目&#xff1a;IMAGDressing-v1 IMAGDressing-v1是一个可定制虚拟着装系统&#xff0c;可以生成逼真的服装并支持场景编辑 特点&#xff1a; 1、支持服装生成&#xff0c;可根据&#xff0c;例如文本提示、图像、姿势等&#xff0c;生成逼真的服装图像&…

GuLi商城-商品服务-API-平台属性-规格参数新增与VO

重写保存方法: @Transactional(rollbackFor = Exception.class) @Ove

Linux驱动编程 - 字符设备驱动

目录 简介&#xff1a; 一、字符设备驱动框架 1、字符设备驱动入口 2、字符设备驱动加载过程 2.1 申请设备号 2.1.1 分配设备号函数 (1) 静态分配函数 (2) 动态分配函数 (3) 注销设备号 2.1.2 设备号中的主/次设备号 2.1.3 申请设备号示例 2.2 注册字符设备 2.2.1 cd…

手摸手教你撕碎西门子S7通讯协议06--S7Read读取short数据

1、S7通讯回顾 - &#xff08;1&#xff09;建立TCP连接 Socket.Connect-》已实现 - &#xff08;2&#xff09;发送访问请求 COTP-》已实现 - &#xff08;3&#xff09;交换通信信息 Setup Communication-》已实现 - &#xff08;4&#xff09;执行相关操作 …

如何使用rdma-core来实现RDMA操作

rdma-core 是一个开源项目&#xff0c;为远程直接内存访问&#xff08;RDMA&#xff09;提供用户空间的支持。它包括 RDMA 设备的驱动程序、库和工具&#xff0c;旨在简化 RDMA 应用的开发和部署。 基础知识参考博文&#xff1a; 一文带你了解什么是RDMA RDMA 高性能架构基本…

Langchain--如何使用大模型 2.0

【&#x1f34a;易编橙终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 Langch…

【已解决】嵌入式linux mobaxterm unable to open connection to comx 串口正常连接,但终端无法输入

1.点击Session重新选择串口&#xff0c;注意看看串口是不是连接到虚拟机&#xff0c;导致串口被占用。 2.选择PC机与开发板连接的串口&#xff0c;不知道的话可以打开设备管理器看看&#xff0c;选择正确的波特率&#xff0c;一般是115200。 3.关键一步&#xff1a;选择后别急…

【计算机网络原理】网络层IP协议的总结和数据链路层以太网协议的总结.

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

MobaXterm tmux 配置妥当

一、事出有因 缘由&#xff1a;接上篇文章&#xff0c;用Docker搭建pwn环境后&#xff0c;用之前学过的多窗口tmux进行调试程序&#xff0c;但是鼠标滚动的效果不按预期上下翻屏。全网搜索很难找到有效解决办法&#xff0c;最后还是找到了一篇英文文章&#xff0c;解决了&…

正点原子imx6ull-mini-Linux设备树下的LED驱动实验(4)

1&#xff1a;修改设备树文件 在根节点“/”下创建一个名为“alphaled”的子节点&#xff0c;打开 imx6ull-alientek-emmc.dts 文件&#xff0c; 在根节点“/”最后面输入如下所示内容 alphaled {#address-cells <1>;#size-cells <1>;compatible "atkalp…

25.惰性队列

介绍 消费者由于各种原因而致使长时间不能消费消息造成堆积。比如有一百万条消息发送到mq中&#xff0c;消费者这时宕机了不能消费消息&#xff0c;造成了消息堆积。惰性队列就有必要了。 正常情况下&#xff0c;消息保存在内存中。消费者从内存中读取消息消费&#xff0c;速…

游戏制作中没想明白的事情

当一个备忘录&#xff0c;有的是还没有时间去深入研究&#xff0c;或者没有从头了解 什么是建模绑定&#xff1f;为什么人物建模&#xff0c;初始化都是双手打开的&#xff1f;平着放武器&#xff0c;但运行的时候武器会自动竖起来&#xff0c;这是怎么做到的&#xff1f; 思…

KamaCoder 100. 岛屿的最大面积 + Leetcode 695. Max Area of Island

题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周都是水域。你可以假设矩阵外均…

XYCTF2024 WP

Pwn&#xff1a; hello_world(签到)&#xff1a; 这里的printf没有格式化字符串漏洞&#xff0c;但是我们依旧可以填充栈来利用printf泄露栈上信息 根据我们能填充的字节数来看&#xff0c;我们无法泄露出libc_start_main128的地址&#xff0c;但是可以泄露libc_start_call_m…

一款免费且功能强大的硬件检测工具,绿色小巧免安装!

HWiNFO是一款免费功能强大且广泛使用的硬件信息检测和监控工具&#xff0c;适用于Windows系统。它能够提供详细的硬件信息&#xff0c;包括CPU、主板、内存、硬盘、显卡等组件的详细规格和性能数据。此外&#xff0c;HWiNFO还支持实时监控硬件状态&#xff0c;如温度、电压和风…

【React Hooks原理 - useTransition】

概述 在上一篇中我们介绍了useDeferredValue的基本原理&#xff0c;本文主要介绍一下useTransition这个Hook&#xff0c;之所以在这里提到useDeferredValue&#xff0c;是因为这两个Hook都是在React18引入的进行渲染优化的Hooks&#xff0c;在某些功能上是重叠的&#xff0c;主…

面试面到自闭,字节软件测试岗五轮面试,四个小时灵魂拷问...

准备过程 我自己是本科毕业后在老东家干了两年多&#xff0c;老东家算是一家”小公司”(毕竟这年头没有 BAT 或 TMD 的 title 都不好意思报出身)&#xff0c;毕业这两年多我也没有在大厂待过&#xff0c;因此找坑的时候是非常非常虚的。迫于心慌&#xff0c;我好好思考了一阵来…

Android 性能优化(二):LeakCanary【用于分析代码是否存在内存泄漏】程序无响应

目录 1&#xff09;内存相关的五种常见问题 2&#xff09;内存溢出和内存泄漏 3&#xff09;LeakCanary是什么? 4&#xff09;LeakCanary如何使用&#xff0c;如何分析&#xff1f; 5&#xff09;LeakCanary监测的内容 提问&#xff1a;程序有时候很卡&#xff0c;经常会出现…

前端开发:Vue2.0桌面组件库-Element

引入Element的步骤&#xff1a; 1.在vscode终端中执行命令&#xff08;需要联网&#xff09; 下载成功 2.在main.js中导入element.ui组件库。 同上&#xff0c;自定义的组件需要先在根组件中引入。 3.访问官网&#xff0c;复制调整代码