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://xiahunao.cn/news/3279913.html

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

相关文章

FMEA在光伏电站安全生产管理中的应用

在绿色能源浪潮席卷全球的今天&#xff0c;光伏电站作为清洁能源的重要支柱&#xff0c;其安全高效运行直接关系到能源供应的稳定与环境的可持续发展。然而&#xff0c;光伏电站的日常运营中潜藏着诸多风险与挑战&#xff0c;如何有效预防事故、保障人员安全及设备稳定运行&…

Aboboo一些操作

常用快捷键⌨ 快捷键/操作方式 功能 鼠标中键/Esc 进入/退出全屏 空格/Tab 暂停/恢复播放 左/右箭头 快退/快进 Ctrl-左/右箭头 30秒快退/快进 Alt-左/右箭头 60秒快退/快进 Ctrl-Alt-左/右箭头 播放速率调节 PageUp/PageDown 上一句/下一句 上下箭头/滚轮 …

算法入门:Java实现排序、查找算法

链接&#xff1a;算法入门&#xff1a;Java实现排序、查找算法 (qq.com) 冒泡/选择/插入/希尔排序代码 (qq.com) 快排/归并/堆排/基数排序代码 (qq.com)

六西格玛管理法

六西格玛管理法是一种旨在提高业务流程效率和减少缺陷的管理策略。它最初由摩托罗拉公司在1980年代末期提出&#xff0c;并随后被通用电气等公司广泛应用和发展。六西格玛的核心理念是通过减少过程变异性来提高产品质量和服务水平。 六西格玛的含义&#xff1a; 统计学概念&am…

【HarmonyOS】鸿蒙中如何获取资源文件的指定类型 fd,string,Uint8Array,RawFileDescriptor

【HarmonyOS】鸿蒙中如何获取资源文件的指定类型 fd&#xff0c;string&#xff0c;Uint8Array&#xff0c;RawFileDescriptor 一、问题背景&#xff1a; 众所周知&#xff0c;在鸿蒙中的资源分为media和rawfile。两者的区别对标android工程一致&#xff0c;后者是其他类型文…

笑谈“八股文”,人生不成文

一、“八股文”在实际工作中是助力、阻力还是空谈&#xff1f; 作为现在各类大中小企业面试程序员时的必问内容&#xff0c;“八股文”似乎是很重要的存在。但“八股文”是否能在实际工作中发挥它“敲门砖”应有的作用呢&#xff1f;有IT人士不禁发出疑问&#xff1a;程序员面试…

未来社交:Facebook如何定义虚拟现实的新时代?

随着科技的飞速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;逐渐从科幻小说中的幻想变成了现实生活中的前沿技术。在这一领域&#xff0c;Facebook&#xff08;现已更名为Meta&#xff09;扮演了重要角色&#xff0c;通过不断的创新和投资&#xff0c;致力于打造一个全…

python爬取某财富网

过程&#xff1a; 点击底部的第3页&#xff0c;第5页&#xff0c;网页刷新了&#xff0c;但是顶部的url地址没有变。那么就是 动态加载&#xff0c; 就是 XHR. 直接请求api. 实验代码如下: import requestsheaders {"User-Agent": "Mozilla/5.0 (Windows NT…

YOLOv10环境搭建、训练自己的目标检测数据集、实际验证和测试

1 环境搭建 1.1 在官方仓库的给定的使用python3.9版本&#xff0c;则使用conda创建对应虚拟环境。 conda create -n yolov10 python3.9 1.2 切换到对应虚拟环境 conda activate yolov10 1.3 在指定目录下克隆yolov10官方仓库代码 git clone https://github.com/THU-MIG/yo…

vue3实战(通用后台管理系统)问题总结

npm install less vue-router element-plus -s elementplus 路由引入组件第二种写法&#xff1a; 使用动态的import( )语法(推荐使用)&#xff08;路由懒加载&#xff09; component:()>import(路径)component:()>import(/views/Main.vue)打包之后的文件将会异常的大&a…

华为云依赖引入错误

问题&#xff1a;记录一次项目加载华为云依赖错误&#xff0c;如下&#xff1a; 错误信息&#xff1a;Could not find artifact com.huawei.storage:esdk-obs-java:pom:3.1.2.1 in bintray-qcloud-maven-repo (https://dl.bintray.com/qcloud/maven-repo/) 找到本地仓库&#…

探索NSL-KDD数据集:入侵检测的起点

引言 在信息安全的世界里&#xff0c;数据集是我们最宝贵的资源。就像厨师离不开食材&#xff0c;数据科学家也离不开数据集。对于入侵检测系统&#xff08;IDS&#xff09;而言&#xff0c;NSL-KDD数据集无疑是一个经典的选择。今天&#xff0c;我们将深入探讨这个数据集&…

摆弄it:越走越深

在英语中&#xff0c;it是一个单词&#xff0c;就是“它”&#xff0c;这是众所周知的事情。今天&#xff0c;我们就来摆弄一下it&#xff0c;摆弄一下“它”&#xff0c;看看能摆弄出什么名堂来。 一、它是它自己 it 大家都知道&#xff0c;同样&#xff0c;itself&#xff0…

地铁深基坑结构施工预警实时监测系统测点布设

01 基坑监测背景 随着我国城市建设的发展&#xff0c;基坑规模和开挖深度不断增加。在基坑开挖过程中&#xff0c;如何尽快的在第一时间了解基坑的变形情况&#xff0c;并动态评估基坑的结构安全&#xff0c;避免事故的发生。与其它监测方法相比&#xff0c;实现自动化监测、信…

【实际源码】工厂进销存管理系统(仓库、采购、生产、销售)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本&#xff0c;并实时掌握各环节的运营状况。 在采购管理方面&#xff0c;系统能够处理采购订单、供应商管理和采购入库等流程&#xff…

LeetCode热题 翻转二叉树、二叉树最大深度、二叉树中序遍历

目录 一、翻转二叉树 1.1 题目链接 1.2 题目描述 1.3 解题思路 二、二叉树最大深度 2.1 题目链接 2.2 题目描述 2.3 解题思路 三、二叉树中序遍历 3.1 题目链接 3.2 题目描述 3.3 解题思路 一、翻转二叉树 1.1 题目链接 翻转二叉树 1.2 题目描述 1.3 解题思路 根…

2024.7.30问题合集

2024.7.30问题合集 1.adb调试出现5037端口被占用的情况2.更改ip地址时出现以下问题3.RV1126 ip配置问题 1.adb调试出现5037端口被占用的情况 问题&#xff1a;5037端口被占用的情况 解决方案&#xff1a;将adb文件下的adb.exe和AdbWinApi.dll两个文件复制到C:\Windows\SysWOW6…

设计模式16-代理模式

设计模式16-代理模式 动机定义与结构模式定义结构 代码推导特点应用总结实例说明1. 远程代理2. 虚拟代理3. 保护代理4. 智能引用代理 动机 在面向对象系统中有一些对象由于某种原因比如对象创建的开销很大或者某些操作需要安全控制&#xff0c;或者需要进程外的访问等情况。直…

【嵌入式之RTOS】死锁问题详解

目录 一、什么是死锁 二、产生死锁的四个必要条件 三、避免死锁的方法 四、实际应用中的考虑 一、什么是死锁 死锁&#xff08;Deadlock&#xff09;是多任务或多线程环境中一个常见的问题&#xff0c;尤其是在实时操作系统&#xff08;RTOS&#xff09;中&#xff0c;如果…

SpringBoot(看这一篇就够了)

目录&#xff1a; SpringBootSpring的缺点什么是SpringBoot&#xff1f;Springboot3 版本要求Springboot的三种构建方式官网搭建通过IDEA脚手架搭建通过Maven搭建项目 SpringBoot的项目结构编写一个测试代码YAML文件自定义配置文件Value读取配置文件ConfigurationProperties读取…