简单的camunda流程处理开发记录

简单的camunda流程处理开发记录

1. 需求

公司使用的是老版本的activiti5xx开发的流程系统,并且微服务中也有workflow模块,但是需要使用webService调用老的流程服务,我自己简单修改了下workflow模块webService调用部分,修改为本地调用,并且流程引擎修改为camunda,基本实现了流程的代办,发起,结束,跳岗,高亮,公司项目没有使用到camunda的外部任务等特性,里面还可以设置租户信息,进行不同租户流程信息隔离,这里简单记录一下开发代码。

2. 主要实现过程

2.1 pom引用

其中camunda标记部分为流程使用需要的包,其余为业务功能使用包

<?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>credit-camunda</artifactId>
        <groupId>cn.git</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>camunda-server</artifactId>
    <description>camunda流程引擎模块</description>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.camunda.bpm</groupId>
                <artifactId>camunda-bom</artifactId>
                <version>7.17.0</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>business-common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>cn.git</groupId>
                    <artifactId>database-synchronize-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>camunda-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>credit-swagger-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>credit-log-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>cache-manage-client</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>credit-discovery-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.ws.rs</groupId>
                    <artifactId>jsr311-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.git</groupId>
            <artifactId>credit-monitor-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-log4j-2.x</artifactId>
            <version>8.6.0</version>
        </dependency>
        <!-- camunda 引擎使用jar包引入 -->
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm</groupId>
            <artifactId>camunda-engine-plugin-spin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.camunda.spin</groupId>
            <artifactId>camunda-spin-dataformat-all</artifactId>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.21</version>
        </dependency>
        <!--groovy发送Http请求,底层是对HTTPClient封装 HttpBuilder-->
        <dependency>
            <groupId>org.codehaus.groovy.modules.http-builder</groupId>
            <artifactId>http-builder</artifactId>
            <version>0.7.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- compiler -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <!-- package -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.2 调用入口实现

调用入口主要包含controller以及Feign接口两部分,主要实现了基本的流程操作功能,功能如下

  • 代办未分类列表信息count查询
  • 代办分类列表信息查询
  • 获取下一岗位信息
  • 获取跟踪列表信息
  • 获取跟踪列表操作员列表信息
  • 获取流程定义信息列表
  • 获取流程任务节点列表信息
  • 流程跳岗信息部署方法
  • 流程参数信息操作
  • 流程图xml获取
  • 流程部署
  • 部署流程列表
  • 等等等
2.2.1 controller实现
package cn.git.camunda.controller;

import cn.git.camunda.dto.*;
import cn.git.camunda.entity.*;
import cn.git.camunda.mapstruct.CamundaConverter;
import cn.git.camunda.page.CamundaFlowPage;
import cn.git.camunda.service.CamundaCommonService;
import cn.git.camunda.util.CamundaTenantEnum;
import cn.git.camunda.vo.*;
import cn.git.common.result.Result;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @description: camunda通用流程方法类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-12 08:46:20
 */
@RestController
@RequestMapping("/flow")
public class CamundaCommonController {

    @Autowired
    private CamundaConverter camundaConverter;

    @Autowired
    private CamundaCommonService camundaCommonService;

    /**
     * 代办未分类列表信息count查询
     *
     * @param userCd 柜员号
     * @param orgCd  机构号
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "代办未分类列表信息count查询", notes = "代办未分类列表信息count查询")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @GetMapping(value = "/listUndoWorkInfoInit")
    public Result<CamundaFlowPage<List<CamundaUndoCount>>> listUndoWorkInfoInit(
            @ApiParam(name = "userCd", value = "柜员号", required = true)
            @RequestParam("userCd") String userCd,
            @ApiParam(name = "camundaTenantEnum", value = "租户组信息", required = true)
            @RequestParam("camundaTenantEnum") CamundaTenantEnum camundaTenantEnum,
            @ApiParam(name = "orgCd", value = "机构号", required = true)
            @RequestParam("orgCd") String orgCd) {
        // 设置查询参数信息
        CamundaUndoCountDTO camundaUndoCountDTO = new CamundaUndoCountDTO();
        camundaUndoCountDTO.setOrgCd(orgCd);
        camundaUndoCountDTO.setUserCd(userCd);
        camundaUndoCountDTO.setCamundaTenantEnum(camundaTenantEnum);
        CamundaFlowPage<List<CamundaUndoCount>> camundaFlowPage = camundaCommonService.getUndoCountList(camundaUndoCountDTO);
        return Result.ok(camundaFlowPage);
    }

    /**
     * 代办分类列表信息查询
     *
     * @param undoTaskListInVO 参数inVO
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "代办分类列表信息查询", notes = "代办分类列表信息查询")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/listUndoTaskList")
    public Result<CamundaUndoTaskListOutVO> listUndoTaskList(
            @Valid @RequestBody CamundaUndoTaskListInVO undoTaskListInVO) {
        // 设置查询参数信息
        CamundaUndoTaskDTO camundaUndoTaskDTO = camundaConverter.undoListInVOtoDTO(undoTaskListInVO);
        CamundaFlowPage<CamundaUndoTask> camundaFlowPage = camundaCommonService.getUndoTaskList(camundaUndoTaskDTO);
        CamundaUndoTaskListOutVO undoTaskListOutVO = new CamundaUndoTaskListOutVO();
        undoTaskListOutVO.setCamundaFlowPage(camundaFlowPage);
        return Result.ok(undoTaskListOutVO);
    }

    /**
     * 结束任务
     *
     * @param endTaskDTO 结束任务DTO
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "结束任务", notes = "结束任务")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/end")
    public Result endTask(@Valid @RequestBody CamundaEndTaskDTO endTaskDTO) {
        camundaCommonService.endProcess(endTaskDTO);
        return Result.ok();
    }

    /**
     * 发起流程
     * @param camundaStartProcessDTO 撤销inVO
     */
    @ApiOperation(value = "新发起一个流程", notes = "新发起一个流程")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/start")
    public Result<CamundaStartRspDTO> startProcess(@Valid @RequestBody CamundaStartProcessDTO camundaStartProcessDTO) {
        CamundaStartRspDTO camundaStartRspDTO = camundaCommonService.startProcess(camundaStartProcessDTO);
        return Result.ok(camundaStartRspDTO);
    }

    /**
     * 正常提交流程
     * @param submitTaskDTO 提交流程dto
     */
    @ApiOperation(value = "正常提交流程", notes = "正常提交流程")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/submit")
    public Result<CamundaSubmitTaskRspDTO> submitNormalTask(@Valid @RequestBody CamundaSubmitTaskDTO submitTaskDTO) {
        CamundaSubmitTaskRspDTO camundaSubmitTaskRspDTO = camundaCommonService.submitNormalTask(submitTaskDTO);
        return Result.ok(camundaSubmitTaskRspDTO);
    }

    /**
     * 流程撤销
     * @param camundaCancelInVO 撤销inVO
     */
    @ApiOperation(value = "流程撤销", notes = "流程撤销")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/cancel")
    public Result cancelProcess(@Valid @RequestBody CamundaCancelInVO camundaCancelInVO) {
        // 封装参数
        CamundaCancelDTO camundaCancelDTO = new CamundaCancelDTO();
        camundaCancelDTO.setProcessId(camundaCancelInVO.getProcessId());
        camundaCancelDTO.setCancelReason(camundaCancelInVO.getCancelReason());
        camundaCancelDTO.setCamundaTenantEnum(camundaCancelInVO.getCamundaTenantEnum());
        camundaCommonService.cancelProcess(camundaCancelDTO);
        return Result.ok();
    }

    /**
     * 驳回任务到上一岗或者发起岗
     * @param rejectTaskDTO 驳回DTO
     */
    @ApiOperation(value = "驳回任务到上一岗或者发起岗", notes = "驳回任务到上一岗或者发起岗")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/reject")
    public Result rejectTaskToStartNode(@Valid @RequestBody CamundaRejectTaskDTO rejectTaskDTO) {
        // 封装参数
        camundaCommonService.rejectTaskToStartNode(rejectTaskDTO);
        return Result.ok();
    }

    /**
     * 流程跳跃节点
     * @param camundaGotoDTO 跳跃节点DTO
     */
    @ApiOperation(value = "流程撤销", notes = "流程撤销")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/gotoTask")
    public Result gotoTask(@Valid @RequestBody CamundaGotoDTO camundaGotoDTO) {
        // 封装参数
        camundaCommonService.gotoDestinationTask(camundaGotoDTO);
        return Result.ok();
    }

    /**
     * 获取下一岗位信息
     *
     * @param nextPositionInVO 参数inVO
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "获取下一岗位信息", notes = "获取下一岗位信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/getNextPositionInfo")
    public Result<CamundaNextPositionRspDTO> getNextPositionInfo(@Valid @RequestBody CamundaNextPositionInVO nextPositionInVO) {
        // 设置查询参数信息
        CamundaNextPositionDTO camundaNextPositionDTO = camundaConverter.nextPositionInVOtoDTO(nextPositionInVO);
        CamundaNextPositionRspDTO rspDTO = camundaCommonService.getNextPositionList(camundaNextPositionDTO);
        return Result.ok(rspDTO);
    }

    /**
     * 获取流程参数信息
     *
     * @param camundaVariableInVO 流程参数信息获取InVO
     */
    @ApiOperation(value = "获取流程参数信息", notes = "获取流程参数信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/task/variables")
    public Result<CamundaVariableOutVO> getTaskVariables(@Valid @RequestBody CamundaVariableInVO camundaVariableInVO) {
        // 封装参数
        CamundaVariablesDTO variablesDTO = new CamundaVariablesDTO();
        variablesDTO.setTaskId(camundaVariableInVO.getTaskId());
        variablesDTO.setVariableKeyList(camundaVariableInVO.getVariableKeyList());
        variablesDTO.setCamundaTenantEnum(camundaVariableInVO.getCamundaTenantEnum());
        Map<String, Object> taskVariableMap = camundaCommonService.getTaskVariables(variablesDTO);

        // 设置响应参数信息
        CamundaVariableOutVO camundaVariableOutVO = new CamundaVariableOutVO();
        camundaVariableOutVO.setVariableMap(taskVariableMap);
        return Result.ok(camundaVariableOutVO);
    }

    /**
     * 删除参数key值
     *
     * @param taskId 参数id
     * @param removeKey 参数key
     */
    @ApiOperation(value = "删除参数key值", notes = "删除参数key值")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @GetMapping(value = "/remove/variable")
    public Result removeParamByKey(
            @ApiParam(name = "removeKey", value = "删除参数的key", required = true)
            @NotBlank(message = "删除参数的key不能为空!")
            @RequestParam("removeKey") String removeKey,
            @ApiParam(name = "camundaTenantEnum", value = "删除参数的key", required = true)
            @NotBlank(message = "租户枚举信息不能为空!")
            @RequestParam("camundaTenantEnum") CamundaTenantEnum camundaTenantEnum,
            @ApiParam(name = "taskId", value = "任务id", required = true)
            @NotBlank(message = "删除参数对应taskId不能为空!")
            @RequestParam("taskId") String taskId) {
        // 进行删除操作
        camundaCommonService.removeVariable(taskId, removeKey, camundaTenantEnum);
        return Result.ok();
    }

    /**
     * 新增或者修改参数
     * @param addOrUpdateVariableInVO 参数inVO
     */
    @ApiOperation(value = "新增或者修改参数,原来没有此参数则新增,否则修改", notes = "新增或者修改参数,原来没有此参数则新增,否则修改")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/add/update/variable")
    public Result addOrUpdateVariableByKey(@Valid @RequestBody CamundaAddOrUpdateVariableInVO addOrUpdateVariableInVO) {
        // 进行删除操作
        CamundaAddOrUpdateVariableDTO addOrUpdateVariableDTO = camundaConverter.addOrUpdateVariableInVOtoDTO(addOrUpdateVariableInVO);
        camundaCommonService.addOrUpdateVariable(addOrUpdateVariableDTO);
        return Result.ok();
    }

    /**
     * 获取跟踪列表信息
     *
     * @param camundaTraceHistoryDTO 参数dto
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "获取跟踪列表信息", notes = "获取跟踪列表信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/getTraceInfo")
    public Result<CamundaFlowPage<List<CamundaTraceHistory>>> getTraceInfo(
            @Valid @RequestBody CamundaTraceHistoryDTO camundaTraceHistoryDTO) {
        // 进行查询
        CamundaFlowPage<List<CamundaTraceHistory>> camundaFlowPage = camundaCommonService
                .getCamundaTracePage(camundaTraceHistoryDTO);
        return Result.ok(camundaFlowPage);
    }

    /**
     * 获取跟踪列表操作员列表信息
     *
     * @param camundaTraceApproveHistoryDTO 参数dto
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "获取跟踪列表操作员列表信息", notes = "获取跟踪列表操作员列表信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/getTraceApproveList")
    public Result<CamundaFlowPage<CamundaTraceApproveHistory>> getTraceApproveList(
            @Valid @RequestBody CamundaTraceApproveHistoryDTO camundaTraceApproveHistoryDTO) {
        // 进行查询
        CamundaFlowPage<CamundaTraceApproveHistory> camundaFlowPage = camundaCommonService
                .getCamundaTraceApprovePage(camundaTraceApproveHistoryDTO);
        return Result.ok(camundaFlowPage);
    }

    /**
     * 获取流程已经部署信息
     *
     * @param  camundaLoadTemplateInVO
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "获取流程已经部署信息", notes = "获取流程已经部署信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/load/template/page")
    public Result<CamundaLoadTemplateOutVO> getLoadTemplatePage(
            @Valid @RequestBody CamundaLoadTemplateInVO camundaLoadTemplateInVO) {
        // 参数赋值操作
        CamundaLoadTemplateDTO templateDTO = new CamundaLoadTemplateDTO();
        templateDTO.setTemplateKey(camundaLoadTemplateInVO.getTemplateKey());
        templateDTO.setTemplateName(camundaLoadTemplateInVO.getTemplateName());
        templateDTO.setVersion(camundaLoadTemplateInVO.getVersion());
        templateDTO.setCamundaTenantEnum(camundaLoadTemplateInVO.getCamundaTenantEnum());

        // 进行查询
        CamundaFlowPage<CamundaLoadTemplate> camundaFlowPage = camundaCommonService
                .getLoadTemplatePage(templateDTO);
        CamundaLoadTemplateOutVO outVO = new CamundaLoadTemplateOutVO();
        outVO.setPageBean(camundaFlowPage);
        return Result.ok(outVO);
    }

    /**
     * 获取流程定义信息列表
     *
     * @param camundaDeployDTO 参数dto
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "获取流程定义信息列表", notes = "获取流程定义信息列表")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/getProcessDeployInfoPage")
    public Result<CamundaFlowPage<ActReProcdef>> getProcessDeployInfoPage(
            @Valid @RequestBody CamundaDeployDTO camundaDeployDTO) {
        // 进行查询
        CamundaFlowPage<ActReProcdef> camundaCommonServiceReProcessDeployPage = camundaCommonService
                .getReProcessDeployPage(camundaDeployDTO);
        return Result.ok(camundaCommonServiceReProcessDeployPage);
    }

    /**
     * 获取流程任务节点列表信息
     *
     * @param camundaJumpPointPageDTO 参数dto
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "获取流程任务节点列表信息", notes = "获取流程任务节点列表信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @PostMapping(value = "/getJumpPointPage")
    public Result<CamundaFlowPage<CamundaJumpPoint>> getJumpPointPage(
            @Valid @RequestBody CamundaJumpPointPageDTO camundaJumpPointPageDTO) {
        // 进行查询
        CamundaFlowPage<CamundaJumpPoint> camundaCommonServiceJumpPointPage = camundaCommonService
                .getJumpPointPage(camundaJumpPointPageDTO);
        return Result.ok(camundaCommonServiceJumpPointPage);
    }

    /**
     * 流程跳岗信息部署方法
     *
     * @param processDefinitionId 流程定义id
     * @param orgCd 机构cd
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "流程跳岗信息部署方法", notes = "流程跳岗信息部署方法")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @GetMapping(value = "/loadProcessDefinition/{processDefinitionId}/{orgCd}")
    public Result loadProcessDefinition(@PathVariable(value = "processDefinitionId") String processDefinitionId,
                                        @PathVariable(value = "orgCd") String orgCd) {
        // 进行查询
        camundaCommonService.loadProcessDefinition(processDefinitionId, orgCd);
        return Result.ok();
    }

    /**
     * 通过版本号,流程定义processDefinitionId获取流程定义xml信息
     *
     * @param processDefinitionKey 流程定义key
     * @param version 版本编号
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "通过版本号,流程定义processDefinitionKey获取流程定义xml信息", notes = "通过版本号,流程定义processDefinitionKey获取流程定义xml信息")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @GetMapping(value = "/definition/xml/{processDefinitionKey}/{version}")
    public Result<String> getDefinitionXMLByKeyAndVersion(
            @PathVariable(value = "processDefinitionKey") String processDefinitionKey,
            @PathVariable(value = "version") Integer version,
            @ApiParam(name = "camundaTenantEnum", value = "租户信息", required = true)
            @RequestParam("camundaTenantEnum") CamundaTenantEnum camundaTenantEnum) throws IOException {
        // 进行查询
        String definitionXMLStr = camundaCommonService.getDefinitionXMLByKeyAndVersion(processDefinitionKey,
                version,
                camundaTenantEnum);
        return Result.ok(definitionXMLStr);
    }

    /**
     * 通过processId下载获取流程xml
     * @param processId
     * @return
     */
    @ApiOperation(value = "通过processId获取流程xml", notes = "通过processId获取流程xml")
    @PostMapping(value = "/definition/xml/{processId}")
    public void downloadFile(
            @ApiParam(name = "processId", value = "流程实例Id", required = true)
            @PathVariable(value = "processId") String processId,
            @ApiParam(name = "camundaTenantEnum", value = "租户枚举信息", required = true)
            @RequestParam("camundaTenantEnum") CamundaTenantEnum camundaTenantEnum) throws IOException {
        // 获取文件
        camundaCommonService.getDefinitionXMLByProcessId(processId, camundaTenantEnum);
    }

    /**
     * 通过processId获取流程图XML字符串
     * @param processId
     * @return
     */
    @ApiOperation(value = "通过processId获取流程图XML字符串", notes = "通过processId获取流程图XML字符串")
    @PostMapping(value = "/definition/str/xml/{processId}")
    public Result<String> getDefinitionXMLStr(
            @ApiParam(name = "processId", value = "流程实例Id", required = true)
            @PathVariable(value = "processId") String processId,
            @ApiParam(name = "camundaTenantEnum", value = "租户枚举信息", required = true)
            @RequestParam(value = "camundaTenantEnum") CamundaTenantEnum camundaTenantEnum) throws IOException {
        // 获取文件
        String xmlStr = camundaCommonService.getDefinitionXMLStrByProcessId(processId, camundaTenantEnum);
        return Result.ok(xmlStr);
    }

    /**
     * 获取流程高亮线等,高亮任务等
     *
     * @param taskId taskId
     * @param orgCd 机构cd
     * @param userCd 柜员编号
     * @return WorkFlowPage 分页对象
     */
    @ApiOperation(value = "流程跳岗信息部署方法", notes = "流程跳岗信息部署方法")
    @ApiResponses({@ApiResponse(code = 1, message = "OK", response = Result.class)})
    @GetMapping(value = "/highlight/lines/{taskId}/{userCd}/{orgCd}")
    public Result<CamundaTaskImageNodeRspDTO> loadProcessDefinition(
            @ApiParam(name = "taskId", value = "任务id", required = true)
            @PathVariable(value = "taskId") String taskId,
            @ApiParam(name = "userCd", value = "柜员cd", required = true)
            @PathVariable(value = "userCd") String userCd,
            @ApiParam(name = "orgCd", value = "机构cd", required = true)
            @PathVariable(value = "orgCd") String orgCd) {
        // 封装参数
        CamundaTaskImageDTO camundaTaskImageDTO = new CamundaTaskImageDTO();
        camundaTaskImageDTO.setTaskId(taskId);
        camundaTaskImageDTO.setUserCd(userCd);
        camundaTaskImageDTO.setOrgCd(orgCd);
        CamundaTaskImageNodeRspDTO rspDTO = camundaCommonService.getCamundaTaskImage(camundaTaskImageDTO);
        return Result.ok(rspDTO);
    }

    /**
     * 通过文件部署流程图, 文件格式为xml
     * @return
     */
    @ApiOperation(value = "直接通过文件部署新流程图", notes = "部署新流程图,返回流程部署成功的流程定义id[processDefinitionId]")
    @PostMapping(value = "/deploy/process")
    public Result<String> uploadProcessDefinition(
            @ApiParam(name = "tenantEnum", value = "租户id", required = true)
            @RequestParam(value = "tenantEnum") CamundaTenantEnum tenantEnum,
            @RequestParam MultipartFile file) throws IOException {
        // 获取文件进行部署
        String processDefinitionId = camundaCommonService.deployProcess(file, tenantEnum);
        return Result.ok(processDefinitionId);
    }

}

2.2.2 feign接口实现

feign接口定义类

package cn.git.camunda.api;

import cn.git.camunda.dto.*;
import cn.git.camunda.entity.CamundaTraceApproveHistory;
import cn.git.camunda.entity.CamundaTraceHistory;
import cn.git.camunda.page.CamundaFlowPage;
import cn.git.camunda.util.CamundaTenantEnum;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.List;

/** 
 * @description: camunda服务feign调用api
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-05-11
 */
@RequestMapping("/flow/feign/api")
public interface CamundaApi {

    /**
     * 发起流程
     * @param startProcessDTO 参数dto
     * @return rspDTO
     */
    @PostMapping("/start")
    CamundaStartRspDTO startProcess(@RequestBody CamundaStartProcessDTO startProcessDTO);


    /**
     * 撤销任务
     * @param camundaCancelDTO 参数dto
     */
    @PostMapping("/cancel")
    void cancelProcess(@RequestBody CamundaCancelDTO camundaCancelDTO);

    /**
     * 任务驳回到发起岗或者上一岗位
     * @param camundaRejectTaskDTO
     * @return
     */
    @PostMapping("/reject")
    Boolean rejectTaskToStartNode(@RequestBody CamundaRejectTaskDTO camundaRejectTaskDTO);

    /**
     * 提交普通任务
     * @param camundaSubmitTaskDTO 参数dto
     * @return
     */
    @PostMapping("/submit")
    CamundaSubmitTaskRspDTO submitNormalTask(@RequestBody CamundaSubmitTaskDTO camundaSubmitTaskDTO);

    /**
     * 结束任务
     * @param endTaskDTO 参数dto
     */
    @PostMapping("/end")
    void endProcess(@RequestBody CamundaEndTaskDTO endTaskDTO);

    /**
     * 获取下一处理岗位信息
     * @param camundaNextPositionDTO
     * @return
     */
    @PostMapping("/position")
    CamundaNextPositionRspDTO getNextPositionList(@RequestBody CamundaNextPositionDTO camundaNextPositionDTO);

    /**
     * 跳转到目标节点
     * @param camundaGotoDTO
     * @return
     */
    @PostMapping("/goto")
    Boolean gotoDestinationTask(@RequestBody CamundaGotoDTO camundaGotoDTO);

    /**
     * 获取流程图XML字符串
     * @param processId 流程实例id
     */
    @GetMapping("/xml")
    String getDefinitionXMLStrByProcessId(@RequestParam("processId") String processId,
                                          @RequestParam("camundaTenantEnum") CamundaTenantEnum camundaTenantEnum)
            throws IOException;

    /**
     * 获取跟踪列表信息
     * @param camundaTraceHistoryDTO 参数dto
     * @return
     */
    @PostMapping("/trace")
    CamundaFlowPage<List<CamundaTraceHistory>> getCamundaTracePage(@RequestBody CamundaTraceHistoryDTO camundaTraceHistoryDTO);

    /**
     * 跟踪列表处理人列表信息
     * @param camundaTraceApproveHistoryDTO 参数dto
     * @return
     */
    @PostMapping("/trace/approve")
    CamundaFlowPage<CamundaTraceApproveHistory> getCamundaTraceApprovePage(@RequestBody CamundaTraceApproveHistoryDTO camundaTraceApproveHistoryDTO);

}

feign接口实现类

package cn.git.camunda.manage;

import cn.git.camunda.api.CamundaApi;
import cn.git.camunda.dto.*;
import cn.git.camunda.entity.CamundaTraceApproveHistory;
import cn.git.camunda.entity.CamundaTraceHistory;
import cn.git.camunda.page.CamundaFlowPage;
import cn.git.camunda.service.CamundaCommonService;
import cn.git.camunda.util.CamundaTenantEnum;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;

/**
 * @description: camundaApi外围feign接口实现类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-05-11
 */
@Api(value = "camundaApi外围feign接口实现类", tags = "camundaApi外围feign接口实现类")
@RestController
public class CamundaApiImpl implements CamundaApi {

    private CamundaCommonService commonService;

    /**
     * 发起流程
     *
     * @param startProcessDTO 参数dto
     * @return rspDTO
     */
    @Override
    public CamundaStartRspDTO startProcess(CamundaStartProcessDTO startProcessDTO) {
        return commonService.startProcess(startProcessDTO);
    }

    /**
     * 撤销任务
     *
     * @param camundaCancelDTO 参数dto
     */
    @Override
    public void cancelProcess(CamundaCancelDTO camundaCancelDTO) {
        commonService.cancelProcess(camundaCancelDTO);
    }

    /**
     * 任务驳回到发起岗或者上一岗位
     *
     * @param camundaRejectTaskDTO
     * @return
     */
    @Override
    public Boolean rejectTaskToStartNode(CamundaRejectTaskDTO camundaRejectTaskDTO) {
        return commonService.rejectTaskToStartNode(camundaRejectTaskDTO);
    }

    /**
     * 提交普通任务
     *
     * @param camundaSubmitTaskDTO 参数dto
     * @return
     */
    @Override
    public CamundaSubmitTaskRspDTO submitNormalTask(CamundaSubmitTaskDTO camundaSubmitTaskDTO) {
        return commonService.submitNormalTask(camundaSubmitTaskDTO);
    }

    /**
     * 结束任务
     *
     * @param endTaskDTO 参数dto
     */
    @Override
    public void endProcess(CamundaEndTaskDTO endTaskDTO) {
        commonService.endProcess(endTaskDTO);
    }

    /**
     * 获取下一处理岗位信息
     *
     * @param camundaNextPositionDTO
     * @return
     */
    @Override
    public CamundaNextPositionRspDTO getNextPositionList(CamundaNextPositionDTO camundaNextPositionDTO) {
        return commonService.getNextPositionList(camundaNextPositionDTO);
    }

    /**
     * 跳转到目标节点
     *
     * @param camundaGotoDTO
     * @return
     */
    @Override
    public Boolean gotoDestinationTask(CamundaGotoDTO camundaGotoDTO) {
        return commonService.gotoDestinationTask(camundaGotoDTO);
    }

    /**
     * 获取流程图XML字符串
     *
     * @param processId 流程实例id
     */
    @Override
    public String getDefinitionXMLStrByProcessId(String processId, CamundaTenantEnum camundaTenantEnum) throws IOException {
        return commonService.getDefinitionXMLStrByProcessId(processId, camundaTenantEnum);
    }

    /**
     * 获取跟踪列表信息
     *
     * @param camundaTraceHistoryDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<List<CamundaTraceHistory>> getCamundaTracePage(CamundaTraceHistoryDTO camundaTraceHistoryDTO) {
        return commonService.getCamundaTracePage(camundaTraceHistoryDTO);
    }

    /**
     * 跟踪列表处理人列表信息
     *
     * @param camundaTraceApproveHistoryDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<CamundaTraceApproveHistory> getCamundaTraceApprovePage(CamundaTraceApproveHistoryDTO camundaTraceApproveHistoryDTO) {
        return commonService.getCamundaTraceApprovePage(camundaTraceApproveHistoryDTO);
    }
}

2.3 service接口

package cn.git.camunda.service;

import cn.git.camunda.dto.*;
import cn.git.camunda.entity.*;
import cn.git.camunda.page.CamundaFlowPage;
import cn.git.camunda.util.CamundaTenantEnum;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @description: camunda通用方法service
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-12 08:58:58
 */
public interface CamundaCommonService {

    /**
     * 发起流程
     * @param startProcessDTO 参数dto
     * @return rspDTO
     */
    CamundaStartRspDTO startProcess(CamundaStartProcessDTO startProcessDTO);

    /**
     * 查询未分类代办列表信息
     * @param camundaUndoCountDTO 参数dto
     * @return
     */
    CamundaFlowPage<List<CamundaUndoCount>> getUndoCountList(CamundaUndoCountDTO camundaUndoCountDTO);

    /**
     * 查询代办分类后单一业务列表信息
     * @param camundaUndoTaskDTO 参数dto
     * @return
     */
    CamundaFlowPage<CamundaUndoTask> getUndoTaskList(CamundaUndoTaskDTO camundaUndoTaskDTO);

    /**
     * 结束任务
     * @param endTaskDTO 参数dto
     */
    void endProcess(CamundaEndTaskDTO endTaskDTO);

    /**
     * 撤销任务
     * @param camundaCancelDTO 参数dto
     */
    void cancelProcess(CamundaCancelDTO camundaCancelDTO);

    /**
     * 提交普通任务
     * @param camundaSubmitTaskDTO 参数dto
     * @return
     */
    CamundaSubmitTaskRspDTO submitNormalTask(CamundaSubmitTaskDTO camundaSubmitTaskDTO);

    /**
     * 获取下一处理岗位信息
     * @param camundaNextPositionDTO
     * @return
     */
    CamundaNextPositionRspDTO getNextPositionList(CamundaNextPositionDTO camundaNextPositionDTO);

    /**
     * 获取任务变量信息
     * @param camundaVariablesDTO 参数DTO
     * @return
     */
    Map<String, Object> getTaskVariables(CamundaVariablesDTO camundaVariablesDTO);

    /**
     * 删除参数key值
     * @param taskId 参数id
     * @param removeKey 参数key
     * @param camundaTenantEnum 租户枚举
     * @return
     */
    void removeVariable(String taskId, String removeKey, CamundaTenantEnum camundaTenantEnum);

    /**
     * 新增或者修改参数
     * @param addOrUpdateVariableDTO 参数DTO
     * @return
     */
    void addOrUpdateVariable(CamundaAddOrUpdateVariableDTO addOrUpdateVariableDTO);

    /**
     * 部署流程定义信息到本地库表中
     * @param processDefinitionId 流程定义id
     * @param orgCd 机构cd
     */
    void loadProcessDefinition(String processDefinitionId, String orgCd);

    /**
     * 操作跳跃节点信息
     * @param camundaJumpPointSetDTO 参数dto
     */
    void optionJumpPoint(CamundaJumpPointSetDTO camundaJumpPointSetDTO);

    /**
     * 获取全部流程定义列表信息
     * @param camundaDeployDTO 参数dto
     * @return
     */
    CamundaFlowPage<ActReProcdef> getReProcessDeployPage(CamundaDeployDTO camundaDeployDTO);

    /**
     * 获取任务节点信息
     * @param jumpPointPageDTO 参数dto
     * @return
     */
    CamundaFlowPage<CamundaJumpPoint> getJumpPointPage(CamundaJumpPointPageDTO jumpPointPageDTO);

    /**
     * 获取跟踪列表信息
     * @param camundaTraceHistoryDTO 参数dto
     * @return
     */
    CamundaFlowPage<List<CamundaTraceHistory>> getCamundaTracePage(CamundaTraceHistoryDTO camundaTraceHistoryDTO);

    /**
     * 跟踪列表处理人列表信息
     * @param camundaTraceApproveHistoryDTO 参数dto
     * @return
     */
    CamundaFlowPage<CamundaTraceApproveHistory> getCamundaTraceApprovePage(CamundaTraceApproveHistoryDTO camundaTraceApproveHistoryDTO);

    /**
     * 获取已经部署流程模板列表信息
     * @param camundaLoadTemplateDTO 参数dto
     * @return
     */
    CamundaFlowPage<CamundaLoadTemplate> getLoadTemplatePage(CamundaLoadTemplateDTO camundaLoadTemplateDTO);

    /**
     * 任务驳回到发起岗或者上一岗位
     * @param camundaRejectTaskDTO
     * @return
     */
    Boolean rejectTaskToStartNode(CamundaRejectTaskDTO camundaRejectTaskDTO);

    /**
     * 跳转到目标节点
     * @param camundaGotoDTO
     * @return
     */
    Boolean gotoDestinationTask(CamundaGotoDTO camundaGotoDTO);

    /**
     * 获取当前任务完成节点,连线,未完成节点信息
     * @param camundaTaskImageDTO 参数dto
     * @return
     */
    CamundaTaskImageNodeRspDTO getCamundaTaskImage(CamundaTaskImageDTO camundaTaskImageDTO);

    /**
     * 通过版本号,流程定义processDefinitionId获取流程定义xml信息
     *
     * @param processDefinitionId 流程定义id
     * @param camundaTenantEnum 租户枚举
     * @param version 版本编号
     */
    String getDefinitionXMLByKeyAndVersion(String processDefinitionId, Integer version, CamundaTenantEnum camundaTenantEnum) throws IOException;

    /**
     * 获取流程图
     * @param processId 流程实例id
     * @param camundaTenantEnum 租户枚举
     */
    void getDefinitionXMLByProcessId(String processId, CamundaTenantEnum camundaTenantEnum) throws IOException;

    /**
     * 获取流程图XML字符串
     * @param processId 流程实例id
     */
    String getDefinitionXMLStrByProcessId(String processId, CamundaTenantEnum camundaTenantEnum) throws IOException;

    /**
     * 部署流程
     * @param file 文件
     * @param tenantEnum 租户
     */
    String deployProcess(MultipartFile file, CamundaTenantEnum tenantEnum) throws IOException;

}

2.4 service接口实现类

这里面主要是流程具体操作的实现类,我这边处理,流程主要跟业务需求关联比较大,所以会有很多业务相关参数,判定条件出现在流程中,主要用于网关参数流向判定

package cn.git.camunda.service.impl;

import cn.git.camunda.consts.CamundaConst;
import cn.git.camunda.dto.*;
import cn.git.camunda.entity.*;
import cn.git.camunda.mapper.ActReProcdefMapper;
import cn.git.camunda.mapper.CamundaJumpPointMapper;
import cn.git.camunda.mapper.CamundaUnfinishedFlowMapper;
import cn.git.camunda.page.CamundaFlowPage;
import cn.git.camunda.page.PageUtil;
import cn.git.camunda.service.CamundaCommonService;
import cn.git.camunda.util.CamundaFlowTypeEnum;
import cn.git.camunda.util.CamundaSysEnum;
import cn.git.camunda.util.CamundaTenantEnum;
import cn.git.camunda.util.CamundaUtil;
import cn.git.common.exception.ServiceException;
import cn.git.common.page.PaginationContext;
import cn.git.common.util.LogUtil;
import cn.git.common.util.WebUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.history.*;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.camunda.bpm.engine.impl.pvm.PvmActivity;
import org.camunda.bpm.engine.impl.pvm.PvmTransition;
import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceModificationInstantiationBuilder;
import org.camunda.bpm.engine.runtime.ProcessInstantiationBuilder;
import org.camunda.bpm.engine.task.Comment;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.engine.variable.VariableMap;
import org.camunda.bpm.engine.variable.Variables;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.impl.instance.ProcessImpl;
import org.camunda.bpm.model.bpmn.impl.instance.UserTaskImpl;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @description: camunda通用方法service
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-12 09:12:05
 */
@Slf4j
@Service
public class CamundaCommonServiceImpl implements CamundaCommonService {

    @Autowired
    private TaskService taskService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private IdentityService identityService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private ProcessEngine processEngine;

    @Autowired
    private CamundaUnfinishedFlowMapper camundaUnfinishedFlowMapper;

    @Autowired
    private ActReProcdefMapper actReProcdefMapper;

    @Autowired
    private CamundaJumpPointMapper camundaJumpPointMapper;

    @Autowired
    private FormService formService;

    @Autowired
    private PageUtil pageUtil;

    @Autowired
    private CamundaUtil camundaUtil;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private WebUtil webUtil;

    /**
     * 发起流程
     *
     * @param startProcessDTO 参数dto
     * @return rspDTO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CamundaStartRspDTO startProcess(CamundaStartProcessDTO startProcessDTO) {
        // 发起流程key
        String businessKey = startProcessDTO.getBusinessKey();
        log.info("businessKey[{}]准备发起流程,入参startProcessDTO为[{}]",
                businessKey,
                JSONObject.toJSONString(startProcessDTO));

        // 设置发起流程操作员标识,以及认证信息(group组信息,tenantId租户信息)
        String creator = startProcessDTO.getUserCd().concat(StrUtil.COLON).concat(startProcessDTO.getOrgCd());
        identityService.setAuthentication(creator,
                Arrays.asList(startProcessDTO.getCamundaTenantEnum().getGroup()),
                Arrays.asList(startProcessDTO.getCamundaTenantEnum().getTenantId()));
        log.info("businessKey[{}]发起流程设置操作员信息成功", businessKey);

        // 设置传递参数信息
        VariableMap variableMap = Variables.createVariables();
        // 流程发起用户id
        variableMap.put(CamundaConst.USER_ID_FLAG, creator);
        // 流程模板id
        variableMap.put(CamundaConst.PROCESS_TEMPLATE_KEY, startProcessDTO.getCamundaFlowTypeEnum().getProcessTemplateKey());
        // 流程业务id
        variableMap.put(CamundaConst.BUSINESS_KEY_FLAG, startProcessDTO.getBusinessKey());
        // 流程业务类型
        variableMap.put(CamundaConst.PROCESS_BIZ_TYPE, startProcessDTO.getCamundaFlowTypeEnum().getBizType());
        // 流程业务名称
        variableMap.put(CamundaConst.PROCESS_BIZ_NAME, startProcessDTO.getCamundaFlowTypeEnum().getBizName());
        // 流程发起人
        variableMap.put(CamundaConst.PROCESS_CREATOR, creator);
        // 设置机构信息
        variableMap.put(CamundaConst.ORG_NAME_FLAG, webUtil.getCurrentOrgName());
        // 客户名称
        variableMap.put(CamundaConst.CUSTOMER_NAME_FLAG, startProcessDTO.getCustomerName());
        // 客户编号
        variableMap.put(CamundaConst.CUSTOMER_NUM_FLAG, startProcessDTO.getCustomerNum());
        // 发起系统来源
        variableMap.put(CamundaConst.SYSTEM, startProcessDTO.getCamundaSysEnum().getSystemFlag());

        // 设置自定义参数信息
        if (ObjectUtil.isNotEmpty(startProcessDTO.getVariableMap())) {
            variableMap.putAll(startProcessDTO.getVariableMap());
            log.info("businessKey[{}]发起流程设置操作参数信息成功,参数为[{}]", businessKey, JSONObject.toJSONString(variableMap));
        }

        // 发起流程
        log.info("businessKey[{}]装备发起流程!", businessKey);
        String templateKey = startProcessDTO.getCamundaFlowTypeEnum().getProcessTemplateKey();
        ProcessInstantiationBuilder builder = runtimeService.createProcessInstanceByKey(templateKey)
                .processDefinitionTenantId(startProcessDTO.getCamundaTenantEnum().getTenantId())
                .businessKey(startProcessDTO.getBusinessKey())
                .setVariables(variableMap);
        ProcessInstance processInstance = builder.execute();

        // 设置响应信息
        CamundaStartRspDTO camundaStartRspDTO = null;
        if (ObjectUtil.isNotNull(processInstance)) {
            camundaStartRspDTO = new CamundaStartRspDTO();
            // 流程processId
            String processInstanceId = processInstance.getProcessInstanceId();

            // 通过 processId,获取task信息进行返回
            Task task = taskService.createTaskQuery()
                    .tenantIdIn(startProcessDTO.getCamundaTenantEnum().getTenantId())
                    .processInstanceId(processInstanceId).singleResult();
            if (ObjectUtil.isNotNull(task)) {
                camundaStartRspDTO.setProcessId(processInstanceId);
                camundaStartRspDTO.setTaskId(task.getId());
                camundaStartRspDTO.setTaskName(task.getName());
                // 当前流程任务定义key
                camundaStartRspDTO.setTaskDefinitionKey(task.getTaskDefinitionKey());
                log.info("businessKey[{}],发起流程后,获取任务信息为[{}]",
                        businessKey,
                        JSONObject.toJSONString(camundaStartRspDTO));

                // 新增业务插入到在途业务表中,自定义代办任务记录表,业务系统方便查询。
                CamundaUnfinishedFlow camundaUnfinishedFlow = new CamundaUnfinishedFlow();
                camundaUnfinishedFlow.setId(IdUtil.simpleUUID());
                camundaUnfinishedFlow.setOptionOrgCd(startProcessDTO.getUserCd());
                camundaUnfinishedFlow.setOptionOrgCd(startProcessDTO.getOrgCd());
                camundaUnfinishedFlow.setCustomerNum(startProcessDTO.getCustomerNum());
                camundaUnfinishedFlow.setCustomerName(startProcessDTO.getCustomerName());
                camundaUnfinishedFlow.setBusinessKey(startProcessDTO.getBusinessKey());
                camundaUnfinishedFlow.setProcessId(processInstanceId);
                camundaUnfinishedFlow.setBizName(startProcessDTO.getCamundaFlowTypeEnum().getBizName());
                camundaUnfinishedFlow.setProcessTemplateKey(startProcessDTO.getCamundaFlowTypeEnum().getProcessTemplateKey());
                if (ObjectUtil.isNotNull(startProcessDTO.getCamundaSysEnum())) {
                    camundaUnfinishedFlow.setSystemFlag(startProcessDTO.getCamundaSysEnum().getSystemFlag());
                } else {
                    camundaUnfinishedFlow.setSystemFlag(CamundaSysEnum.DICS.getSystemFlag());
                }
                camundaUnfinishedFlowMapper.insert(camundaUnfinishedFlow);

            }
        }
        return camundaStartRspDTO;
    }

    /**
     * 通过柜员编号和机构编号获取代办未分类列表信息
     *
     * @param camundaUndoCountDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<List<CamundaUndoCount>> getUndoCountList(CamundaUndoCountDTO camundaUndoCountDTO) {
        // 创建一个查询接口,进行传入操作标识人员代办任务查询
        String userId = camundaUndoCountDTO.getUserCd().concat(StrUtil.COLON).concat(camundaUndoCountDTO.getOrgCd());
        TaskQuery taskQuery = taskService.createTaskQuery()
                // 具体操作人员标识
                .taskAssignee(userId)
                // 未被挂起任务
                .active()
                // 租户信息
                .tenantIdIn(camundaUndoCountDTO.getCamundaTenantEnum().getTenantId())
                // 获取参数信息
                .matchVariableNamesIgnoreCase();

        // 设定查询模板类型
        if (StrUtil.isNotBlank(camundaUndoCountDTO.getProcessTemplateKey())) {
            taskQuery.processDefinitionKey(camundaUndoCountDTO.getProcessTemplateKey());
        }

        // 查询带如下参数的流程信息
        Map<String, Object> paramMap = camundaUndoCountDTO.getParamMap();
        if (ObjectUtil.isNotEmpty(paramMap)) {
            for (String key : camundaUndoCountDTO.getParamMap().keySet()) {
                taskQuery.processVariableValueEquals(key, paramMap.get(key));
            }
        }

        // 计算从哪条开始取,如何取出数据
        List<Task> taskList = taskQuery.list();

        // 对代办数据进行分类求count处理
        List<CamundaUndoCount> camundaUndoCountList = new ArrayList<>();
        if (ObjectUtil.isNotEmpty(taskList)) {
            // 获取所有任务代办任务类型数据
            List<String> processIdList = taskList.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
            QueryWrapper<CamundaUnfinishedFlow> unfinishedFlowQueryWrapper = new QueryWrapper<>();
            unfinishedFlowQueryWrapper.lambda().in(CamundaUnfinishedFlow::getProcessId, processIdList);
            List<CamundaUnfinishedFlow> unfinishedFlowList = camundaUnfinishedFlowMapper
                    .selectList(unfinishedFlowQueryWrapper);
            // 将查询数据转换为map信息
            Map<String, CamundaUnfinishedFlow> unfinishedFlowMap = unfinishedFlowList.stream().collect(
                    Collectors.toMap(
                            CamundaUnfinishedFlow::getProcessId, Function.identity(), (k1, k2) -> k1
                    )
            );
            Map<String, CamundaUndoCount> undoCountMap = new HashMap<>(CamundaConst.INT_16);
            taskList.forEach(task -> {
                // 对任务进行分类计算count信息
                CamundaUnfinishedFlow unfinishedFlow = unfinishedFlowMap.get(task.getProcessInstanceId());
                if (ObjectUtil.isNotNull(unfinishedFlow)) {
                    // 获取流程模板id以及流程模板枚举类型
                    String processTemplateKey = unfinishedFlow.getProcessTemplateKey();
                    CamundaFlowTypeEnum flowTypeEnum = camundaUtil.getCamundaEnumByTemplateKey(processTemplateKey);
                    if (ObjectUtil.isNotNull(flowTypeEnum)) {
                        // 如果此类型有值,则count数进行加一
                        if (ObjectUtil.isNotNull(undoCountMap.get(processTemplateKey))) {
                            CamundaUndoCount currentUndoCount = undoCountMap.get(processTemplateKey);
                            currentUndoCount.setBizCount(currentUndoCount.getBizCount() + 1);
                            undoCountMap.put(processTemplateKey, currentUndoCount);
                        } else {
                            // 首次添加
                            CamundaUndoCount camundaUndoCount = new CamundaUndoCount();
                            camundaUndoCount.setBizCount(CamundaConst.INT_1);
                            camundaUndoCount.setBizTypeName(flowTypeEnum.getBizName());
                            camundaUndoCount.setBizType(flowTypeEnum.getBizType());
                            undoCountMap.put(processTemplateKey, camundaUndoCount);
                        }
                    }
                }
            });
            // 封装返回信息list
            if (ObjectUtil.isNotEmpty(undoCountMap)) {
                undoCountMap.forEach((key, value) -> {
                    camundaUndoCountList.add(value);
                });
            }
        }

        return pageUtil.setFlowListPage(camundaUndoCountList, PaginationContext.getPageNum(), PaginationContext.getPageSize());
    }



    /**
     * 查询代办分类后单一业务列表信息
     *
     * @param camundaUndoTaskDTO 参数dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CamundaFlowPage<CamundaUndoTask> getUndoTaskList(CamundaUndoTaskDTO camundaUndoTaskDTO) {
        // 创建一个查询接口,进行传入操作标识人员代办任务查询
        String userId = camundaUndoTaskDTO.getUserCd().concat(StrUtil.COLON).concat(camundaUndoTaskDTO.getOrgCd());
        TaskQuery taskQuery = taskService.createTaskQuery()
                // 具体操作人员标识
                .taskAssignee(userId)
                // 未被挂起任务
                .active()
                // 租户信息
                .tenantIdIn(camundaUndoTaskDTO.getCamundaTenantEnum().getTenantId())
                // 获取参数信息
                .matchVariableNamesIgnoreCase()
                .orderByTaskCreateTime()
                .desc();

        // 设定查询模板类型
        String processTemplateKey = camundaUndoTaskDTO.getProcessTemplateKey();
        if (StrUtil.isNotBlank(processTemplateKey)) {
            taskQuery.processDefinitionKey(processTemplateKey);
        } else {
            throw new ServiceException("单一业务模板查询必填!");
        }

        // 查询总条数信息
        int totalCount = (int) taskQuery.count();
        // 分页查询
        int firstResultNum = pageUtil.getStartSizeNum(PaginationContext.getPageNum(), PaginationContext.getPageSize());
        List<Task> taskList = taskQuery.listPage(firstResultNum, PaginationContext.getPageSize());

        // 查询获取列表信息,封装最终返回信息
        List<CamundaUndoTask> camundaUndoTaskList = new ArrayList<>();
        if (ObjectUtil.isNotEmpty(taskList)) {
            taskList.forEach(task -> {
                // 获取参数信息以及业务枚举类型
                Map<String, Object> variableMap = runtimeService.getVariables(task.getExecutionId());
                CamundaFlowTypeEnum camundaFlowTypeEnum = camundaUtil.getCamundaEnumByTemplateKey(processTemplateKey);

                // 封装返回类型信息
                CamundaUndoTask camundaUndoTask = new CamundaUndoTask();
                camundaUndoTask.setTaskId(task.getId());
                camundaUndoTask.setOrgCd(StrUtil.toString(variableMap.get(CamundaConst.PROCESS_CREATOR)));
                camundaUndoTask.setOrgName(StrUtil.toString(variableMap.get(CamundaConst.ORG_NAME_FLAG)));
                camundaUndoTask.setProcessId(task.getProcessInstanceId());
                camundaUndoTask.setBizType(camundaFlowTypeEnum.getBizType());
                camundaUndoTask.setBizName(camundaFlowTypeEnum.getBizName());
                camundaUndoTask.setProcessTemplateKey(camundaFlowTypeEnum.getProcessTemplateKey());
                camundaUndoTask.setBusinessKey(StrUtil.toString(variableMap.get(CamundaConst.BUSINESS_KEY_FLAG)));
                camundaUndoTask.setActivityId(task.getTaskDefinitionKey());
                camundaUndoTask.setCreator(StrUtil.toString(variableMap.get(CamundaConst.PROCESS_CREATOR)));
                camundaUndoTask.setProcessCreateDate(task.getCreateTime());
                camundaUndoTask.setCustomerName(StrUtil.toString(variableMap.get(CamundaConst.CUSTOMER_NAME_FLAG)));
                camundaUndoTask.setCustomerNum(StrUtil.toString(variableMap.get(CamundaConst.CUSTOMER_NAME_FLAG)));
                camundaUndoTaskList.add(camundaUndoTask);
            });
        }
        CamundaFlowPage<CamundaUndoTask> camundaFlowPage = new CamundaFlowPage<>();
        camundaFlowPage.setPageSize(PaginationContext.getPageSize());
        camundaFlowPage.setPageNum(PaginationContext.getPageNum());

        // 返回分页信息
        return pageUtil.setCountFlowListPage(camundaUndoTaskList,
                PaginationContext.getPageNum(),
                PaginationContext.getPageSize(),
                totalCount);
    }

    /**
     * 结束任务
     *
     * @param endTaskDTO 参数dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void endProcess(CamundaEndTaskDTO endTaskDTO) {
        // 获取任务信息以及流程实例信息
        Task task = taskService.createTaskQuery()
                // 租户id
                .tenantIdIn(endTaskDTO.getCamundaTenantEnum().getTenantId())
                // 任务id
                .taskId(endTaskDTO.getTaskId())
                .singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]以及租户id[{}]获取任务信息为空!"));
        }

        // 获取流程实例信息
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                // 流程实例id
                .processInstanceId(task.getProcessInstanceId())
                // 租户信息
                .tenantIdIn(endTaskDTO.getCamundaTenantEnum().getTenantId())
                .singleResult();

        // 空值校验
        if (ObjectUtil.isNull(processInstance)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]以及租户id[{}]获取流程实例信息为空,请确认!",
                    task.getProcessInstanceId(),
                    endTaskDTO.getCamundaTenantEnum().getTenantId()));
        }

        // 流程定义id
        String processId = processInstance.getProcessInstanceId();

        // 获取流程定义信息,获取结束任务对应节点,如果强制结束则直接获取流程中end结束节点进行结束任务
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
                .getProcessDefinition(processInstance.getProcessDefinitionId());
        List<ActivityImpl> activityList = processDefinition.getActivities();

        // 强制结束分支,如果一个节点没有 OutgoingTransitions 则为结束节点
        ActivityImpl activityEndPoint = activityList.stream().filter(filterAct ->
                ObjectUtil.isEmpty(filterAct.getOutgoingTransitions())
        ).findFirst().orElse(null);

        // 设置评论信息
        if (StrUtil.isNotBlank(endTaskDTO.getOpinion())) {
            taskService.createComment(task.getId(), task.getProcessInstanceId(), endTaskDTO.getOpinion());
        }

        // 获取当前节点
        ActivityImpl currentActivity = processDefinition.findActivity(task.getTaskDefinitionKey());

        // 进行任务结束处理,判定当前节点是否为end节点
        if (currentActivity.getActivityId().equals(activityEndPoint.getActivityId())) {
            // 非强制结束,当前节点为end节点,调用complete方法即可
            if (ObjectUtil.isNotEmpty(endTaskDTO.getParamsMap())) {
                taskService.complete(task.getId(), endTaskDTO.getParamsMap());
            } else {
                taskService.complete(task.getId());
            }
        } else {
            // 非end节点,进行强制结束,不是在结束任务,需要任务流转(跳节点),判断流转节点是否为空
            if (ObjectUtil.isNotEmpty(endTaskDTO.getParamsMap())) {
                // 带参数结束任务
                runtimeService.createProcessInstanceModification(processInstance.getProcessInstanceId())
                        // 取消当前节点所有活动中的Task任务
                        .cancelAllForActivity(currentActivity.getActivityId())
                        // 目标节点Id,在流程图中看
                        .startBeforeActivity(activityEndPoint.getActivityId())
                        // 参数信息
                        .setVariables(endTaskDTO.getParamsMap())
                        // 意见信息
                        .setAnnotation(endTaskDTO.getOpinion())
                        .execute();
            } else {
                // 不带参数结束任务
                runtimeService.createProcessInstanceModification(processInstance.getProcessInstanceId())
                        // 取消当前节点所有活动中的Task任务
                        .cancelAllForActivity(currentActivity.getActivityId())
                        // 目标节点Id,在流程图中看
                        .startBeforeActivity(activityEndPoint.getActivityId())
                        // 意见信息
                        .setAnnotation(endTaskDTO.getOpinion())
                        .execute();
            }

            // 未完成任务列表信息设置为删除状态
            QueryWrapper<CamundaUnfinishedFlow> unfinishedFlowQueryWrapper = new QueryWrapper<>();
            unfinishedFlowQueryWrapper.lambda().eq(CamundaUnfinishedFlow::getProcessId, processId);
            camundaUnfinishedFlowMapper.delete(unfinishedFlowQueryWrapper);
        }
    }

    /**
     * 撤销任务
     *
     * @param camundaCancelDTO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelProcess(CamundaCancelDTO camundaCancelDTO) {
        // 获取流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .tenantIdIn(camundaCancelDTO.getCamundaTenantEnum().getTenantId())
                .processInstanceId(camundaCancelDTO.getProcessId())
                .singleResult();

        if (ObjectUtil.isNull(processInstance)) {
            throw new ServiceException(StrUtil.format("通过流程实例id[{}]以及租户id[{}]获取流程实例为空,撤销失败!",
                    camundaCancelDTO.getProcessId(),
                    camundaCancelDTO.getCamundaTenantEnum().getTenantId()));
        }

        // 进行撤销操作
        runtimeService.deleteProcessInstance(camundaCancelDTO.getProcessId(), camundaCancelDTO.getCancelReason());

        // 未完成任务列表信息设置为删除状态
        QueryWrapper<CamundaUnfinishedFlow> unfinishedFlowQueryWrapper = new QueryWrapper<>();
        unfinishedFlowQueryWrapper.lambda().eq(CamundaUnfinishedFlow::getProcessId, camundaCancelDTO.getProcessId());
        camundaUnfinishedFlowMapper.delete(unfinishedFlowQueryWrapper);
    }

    /**
     * 提交普通任务
     *
     * @param camundaSubmitTaskDTO 参数dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CamundaSubmitTaskRspDTO submitNormalTask(CamundaSubmitTaskDTO camundaSubmitTaskDTO) {
        // 获取task任务信息
        Task task = taskService.createTaskQuery()
                // 租户id
                .tenantIdIn(camundaSubmitTaskDTO.getCamundaTenantEnum().getTenantId())
                // 任务id
                .taskId(camundaSubmitTaskDTO.getTaskId()).singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new ServiceException("提交普通任务,获取task信息为空!");
        }
        // 获取流程实例信息
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                // 租户id
                .tenantIdIn(camundaSubmitTaskDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
        if (ObjectUtil.isNull(processInstance)) {
            throw new ServiceException("提交普通任务,获取流程实例为空!");
        }

        // 设置评论信息
        String approver = camundaSubmitTaskDTO.getNextUserCd().concat(StrUtil.COLON).concat(camundaSubmitTaskDTO.getNextOrgCd());

        // 设置认证身份信息
        identityService.setAuthentication(approver,
                Arrays.asList(camundaSubmitTaskDTO.getCamundaTenantEnum().getGroup()),
                Arrays.asList(camundaSubmitTaskDTO.getCamundaTenantEnum().getTenantId()));

        // 将处理人填入
        runtimeService.setVariable(task.getExecutionId(), "approver", approver);

        // 设置评论信息
        taskService.createComment(camundaSubmitTaskDTO.getTaskId(), processInstance.getProcessInstanceId(), camundaSubmitTaskDTO.getOpinion());

        // 正常提交任务信息
        if (ObjectUtil.isNotEmpty(camundaSubmitTaskDTO.getVariableMap())) {
            taskService.complete(camundaSubmitTaskDTO.getTaskId(), camundaSubmitTaskDTO.getVariableMap());
        } else {
            taskService.complete(camundaSubmitTaskDTO.getTaskId());
        }

        // 获取提交之后的task信息
        List<Task> taskList = taskService.createTaskQuery()
                // 租戶信息
                .tenantIdIn(camundaSubmitTaskDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(processInstance.getProcessInstanceId())
                .orderByFollowUpDate()
                .desc()
                .list();
        CamundaSubmitTaskRspDTO camundaSubmitTaskRspDTO = null;
        if (ObjectUtil.isNotEmpty(taskList)) {
            // 设定任务执行人信息
            Task currentTask = taskList.get(CamundaConst.INT_0);
            if (ObjectUtil.isNotNull(currentTask) && StrUtil.isBlank(task.getAssignee())) {
                // 类似于setAssignee,设定执行人信息
                taskService.claim(currentTask.getId(), approver);
            }
            // 设置下一处理task任务信息
            camundaSubmitTaskRspDTO = new CamundaSubmitTaskRspDTO();
            camundaSubmitTaskRspDTO.setNextTaskId(currentTask.getId());
            camundaSubmitTaskRspDTO.setNextTaskName(currentTask.getName());
            camundaSubmitTaskRspDTO.setTaskDefKey(currentTask.getTaskDefinitionKey());
        }
        return camundaSubmitTaskRspDTO;
    }

    /**
     * 获取下一处理岗位信息
     *
     * @param camundaNextPositionDTO
     * @return
     */
    @Override
    public CamundaNextPositionRspDTO getNextPositionList(CamundaNextPositionDTO camundaNextPositionDTO) {
        // 最终响应参数信息
        CamundaNextPositionRspDTO positionRspDTO = new CamundaNextPositionRspDTO();

        // 通过taskId获取task实例信息
        Task task = taskService.createTaskQuery()
                // 租户id
                .tenantIdIn(camundaNextPositionDTO.getCamundaTenantEnum().getTenantId())
                // 任务id
                .taskId(camundaNextPositionDTO.getTaskId())
                .singleResult();

        // 空值校验
        if (ObjectUtil.isNull(task)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]以及租户id[{}]获取下一处理岗位获取task实例信息为空!",
                    camundaNextPositionDTO.getTaskId(),
                    camundaNextPositionDTO.getCamundaTenantEnum().getTenantId()));
        }
        positionRspDTO.setCurrentPositionDefinitionKey(task.getTaskDefinitionKey());

        // 获取流程实信息
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                // 租户id
                .tenantIdIn(camundaNextPositionDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();

        // 空值校验
        if (ObjectUtil.isNull(processInstance)) {
            throw new ServiceException(StrUtil.format("通过流程实例id[{}]以及租户id[{}]获取流程定义信息获取为空!",
                    task.getProcessInstanceId(),
                    camundaNextPositionDTO.getCamundaTenantEnum().getTenantId()));
        }

        // 获取整体流程定义信息
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
                .getProcessDefinition(processInstance.getProcessDefinitionId());

        // 获取当前节点定义信息
        ActivityImpl currentActivity = processDefinition.findActivity(task.getTaskDefinitionKey());

        // 获取当前节点传入参数信息
        Map<String, Object> variablesMap = runtimeService.getVariables(task.getExecutionId());

        // 最终获取全部下一处理岗位定义节点
        List<PvmActivity> finalActivityList = new ArrayList<>();

        // 获取当前节点对外指向,并且对指向进行分析
        List<PvmTransition> transitionList = currentActivity.getOutgoingTransitions();
        if (ObjectUtil.isNotEmpty(transitionList)) {
            for (PvmTransition transition : transitionList) {
                // 获取目标节点
                PvmActivity destinationActivity = transition.getDestination();
                String activityNodeType = StrUtil.toString(destinationActivity.getProperty(CamundaConst.CAMUNDA_NODE_TYPE));

                // 目标节点不同类型进行不同处理,
                if (CamundaConst.EXCLUSIVE_GATEWAY.equals(activityNodeType) || CamundaConst.INCLUSIVE_GATEWAY.equals(activityNodeType)) {
                    // 注意此处为 节点->排他网关/包含网关->节点 形式,更复杂形式需要针对业务进行调整
                    List<PvmActivity> exclusiveGateActivityList = getNextPositionByExclusiveGateway(destinationActivity, variablesMap);
                    finalActivityList.addAll(exclusiveGateActivityList);
                } else if (CamundaConst.PARALLEL_GATEWAY.equals(activityNodeType)) {
                    // 并行网关 节点->并行网关->节点
                    List<PvmActivity> parallelGateActivityList = getNextPositionByParallelGateway(destinationActivity);
                    finalActivityList.addAll(parallelGateActivityList);
                } else if (CamundaConst.USER_TASK.equals(activityNodeType)) {
                    // 普通用户任务 节点->节点
                    finalActivityList.add(destinationActivity);
                } else {
                    throw new ServiceException(StrUtil.format("当前获取下一岗位信息暂时activityNodeType[{}]暂时不支持!", activityNodeType));
                }
            }

            // 返回信息进行封装
            List<CamundaNextPositionRspDTO.NextPosition> nextPositionList = new ArrayList<>();
            if (ObjectUtil.isNotEmpty(finalActivityList)) {
                nextPositionList = finalActivityList.stream().map(pvmActivity -> {
                    ActivityImpl activity = (ActivityImpl)pvmActivity;
                    CamundaNextPositionRspDTO.NextPosition nextPosition = new CamundaNextPositionRspDTO.NextPosition();
                    nextPosition.setNextPositionName(activity.getName());
                    nextPosition.setNextPositionDefinitionKey(activity.getActivityId());
                    return nextPosition;
                }).collect(Collectors.toList());
            }

            // 查看是否有跳岗信息
            String processDefinitionId = task.getProcessDefinitionId();
            ActReProcdef actReProcdef = actReProcdefMapper.selectById(processDefinitionId);
            if (ObjectUtil.isNull(actReProcdef)) {
                throw new ServiceException(StrUtil.format("通过流程定义id[{}]获取流程实例定义信息为空!",
                        processDefinitionId));
            }
            // 获取版本,机构,模板key,机构以及task定义id查询跳岗信息
            QueryWrapper<CamundaJumpPoint> jumpPointQueryWrapper = new QueryWrapper<>();
            jumpPointQueryWrapper.lambda().eq(CamundaJumpPoint::getProcessTemplateKey, actReProcdef.getKey())
                    .eq(CamundaJumpPoint::getTemplateVersion, actReProcdef.getVersion())
                    .eq(CamundaJumpPoint::getActivityId, task.getTaskDefinitionKey())
                    .eq(CamundaJumpPoint::getOwnOrg, camundaNextPositionDTO.getOrgCd());
            CamundaJumpPoint camundaJumpPoint = camundaJumpPointMapper.selectOne(jumpPointQueryWrapper);
            // 如果当前没有获取到配置的机构,则向上获取上级机构配置,直到最顶级配置机构0001,如果没有配置,则认为当前你岗位不能跳岗,整体跳岗原则为就近原则
            if (ObjectUtil.isNotNull(camundaJumpPoint) && StrUtil.isNotBlank(camundaJumpPoint.getJumpActivityIds())) {
                // 获取跳岗信息字符串,多个岗位使用逗号分隔
                List<String> activityIdList = Arrays.asList(camundaJumpPoint.getJumpActivityIds().split(StrUtil.COMMA).clone());
                jumpPointQueryWrapper = new QueryWrapper<>();
                jumpPointQueryWrapper.lambda().in(CamundaJumpPoint::getProcessTemplateKey, actReProcdef.getKey())
                        .eq(CamundaJumpPoint::getTemplateVersion, actReProcdef.getVersion())
                        .in(CamundaJumpPoint::getActivityId, activityIdList)
                        .eq(CamundaJumpPoint::getOwnOrg, camundaNextPositionDTO.getOrgCd());
                List<CamundaJumpPoint> camundaJumpPointList = camundaJumpPointMapper.selectList(jumpPointQueryWrapper);
                if (ObjectUtil.isNotEmpty(camundaJumpPointList)) {
                    // 封装跳岗信息
                    List<CamundaNextPositionRspDTO.NextPosition> jumpPositionList = camundaJumpPointList.stream().map(jumpPoint -> {
                        CamundaNextPositionRspDTO.NextPosition nextPosition = new CamundaNextPositionRspDTO.NextPosition();
                        nextPosition.setNextPositionName(jumpPoint.getActivityName());
                        nextPosition.setNextPositionDefinitionKey(jumpPoint.getActivityId());
                        return nextPosition;
                    }).collect(Collectors.toList());
                    // 添加跳岗信息
                    nextPositionList.addAll(jumpPositionList);
                }
            }
            positionRspDTO.setNextPositionList(nextPositionList);
        }

        return positionRspDTO;
    }

    /**
     * 获取任务变量信息
     *
     * @param camundaVariablesDTO 参数DTO
     * @return
     */
    @Override
    public Map<String, Object> getTaskVariables(CamundaVariablesDTO camundaVariablesDTO) {
        // 通过taskId获取task实例信息
        Task task = taskService.createTaskQuery()
                // 租户信息
                .tenantIdIn(camundaVariablesDTO.getCamundaTenantEnum().getTenantId())
                // 任务id
                .taskId(camundaVariablesDTO.getTaskId())
                .singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]以及租户id[{}]获取任务实例失败,请确认taskId传入正确!",
                    camundaVariablesDTO.getTaskId(),
                    camundaVariablesDTO.getCamundaTenantEnum().getTenantId()));
        }

        // 获取参数信息
        Map<String, Object> variablesMap = taskService.getVariables(camundaVariablesDTO.getTaskId());
        if (ObjectUtil.isNotEmpty(variablesMap) && ObjectUtil.isNotEmpty(camundaVariablesDTO.getVariableKeyList())) {
            // 进行值过滤,过滤非筛选值
            Map<String, Object> innerMap = variablesMap.entrySet().stream().filter(entry ->
                    camundaVariablesDTO.getVariableKeyList().contains(entry.getKey())
            ).collect(Collectors.toMap(
                    Map.Entry::getKey, Map.Entry::getValue)
            );
            return innerMap;
        }

        return variablesMap;
    }

    /**
     * 删除参数key值
     *
     * @param taskId 任务id
     * @param removeKey 参数key
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeVariable(String taskId, String removeKey, CamundaTenantEnum tenantEnum) {
        // 获取task实例信息
        Task task = taskService.createTaskQuery()
                // 租户信息
                .tenantIdIn(tenantEnum.getTenantId())
                // 任务id
                .taskId(taskId).singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new ServiceException(StrUtil.format("删除参数通过taskId[{}]以及租户id[{}]查询task实例信息为空!",
                    taskId,
                    tenantEnum.getTenantId()));
        }
        taskService.removeVariable(taskId, removeKey);
    }

    /**
     * 新增或者修改参数
     *
     * @param addOrUpdateVariableDTO 参数dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addOrUpdateVariable(CamundaAddOrUpdateVariableDTO addOrUpdateVariableDTO) {
        // 获取taskId以及租户信息
        String taskId = addOrUpdateVariableDTO.getTaskId();
        String tenantId = addOrUpdateVariableDTO.getCamundaTenantEnum().getTenantId();

        // 获取任务实例信息,空值判定
        Task task = taskService.createTaskQuery()
                .tenantIdIn(tenantId)
                .taskId(taskId)
                .singleResult();
        if (ObjectUtil.isNull(task)) {
            throw new ServiceException(StrUtil.format("修改参数通过taskId[{}]以及租户id[{}]查询task实例信息为空!",
                    taskId,
                    tenantId));
        }

        // 修改参数
        taskService.setVariable(taskId, addOrUpdateVariableDTO.getParamKey(), addOrUpdateVariableDTO.getParamValue());
    }

    /**
     * 部署流程定义信息到本地库表中
     *
     * @param processDefinitionId 流程实例key
     * @param orgCd 配置属于哪个机构
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void loadProcessDefinition(String processDefinitionId, String orgCd) {
        // 流程定义信息
        BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(processDefinitionId);
        if(ObjectUtil.isNull(bpmnModelInstance)){
            throw new ServiceException(StrUtil.format("通过processDefinitionId[{}]查询流程定义实例信息为空", processDefinitionId));
        }

        // 获取process定义信息,服务名称
        String processDefinitionName = null;
        String processDefinitionKey = null;
        Collection<Process> processCollection = bpmnModelInstance.getModelElementsByType(Process.class);
        if (ObjectUtil.isNotNull(processCollection)) {
            Optional<Process> optionalProcess = processCollection.stream().findFirst();
            ProcessImpl processImpl = (ProcessImpl) optionalProcess.get();
            // 获取流程定义名称 eg: camunda流程测试请假
            processDefinitionName = processImpl.getName();
            // 获取流程定义key eg: getRestDays
            processDefinitionKey = processImpl.getId();
        }

        // 获取流程定义信息
        ActReProcdef actReProcdef = actReProcdefMapper.selectById(processDefinitionId);
        if (ObjectUtil.isNull(actReProcdef)) {
            throw new ServiceException(StrUtil.format("通过processDefinitionId[{}]查询流程定义实例信息为空", processDefinitionId));
        }

        // 删除原有信息
        QueryWrapper<CamundaJumpPoint> jumpPointQueryWrapper = new QueryWrapper<>();
        jumpPointQueryWrapper.lambda().eq(CamundaJumpPoint::getProcessTemplateKey, processDefinitionKey)
                .eq(CamundaJumpPoint::getOwnOrg, orgCd)
                .eq(CamundaJumpPoint::getTemplateVersion, actReProcdef.getVersion());
        camundaJumpPointMapper.delete(jumpPointQueryWrapper);

        // 获取所有userTask任务节点信息
        Collection<UserTask> userTaskCollection = bpmnModelInstance.getModelElementsByType(UserTask.class);

        // 整合任务节点信息
        List<UserTaskImpl> userTaskImplList = null;
        if (ObjectUtil.isNotEmpty(userTaskCollection)) {
            userTaskImplList = userTaskCollection.stream().map(userTask -> {
                UserTaskImpl userTaskImpl = (UserTaskImpl) userTask;
                return userTaskImpl;
            }).collect(Collectors.toList());
        }

        // 开始进行落数
        String finalProcessDefinitionKey = processDefinitionKey;
        String finalProcessDefinitionName = processDefinitionName;
        userTaskImplList.forEach(userTask -> {
            // 获取流程task任务定义id以及定义name
            String activityId = userTask.getId();
            String activityName = userTask.getName();

            // 封装跳转参数信息
            CamundaJumpPoint camundaJumpPoint = new CamundaJumpPoint();
            camundaJumpPoint.setId(IdUtil.simpleUUID());
            // 所属机构
            camundaJumpPoint.setOwnOrg(orgCd);
            // 流程任务节点定义id
            camundaJumpPoint.setActivityId(activityId);
            // 任务节点定义名称
            camundaJumpPoint.setActivityName(activityName);
            // 模板key
            camundaJumpPoint.setProcessTemplateKey(finalProcessDefinitionKey);
            // 模板名称
            camundaJumpPoint.setProcessTemplateName(finalProcessDefinitionName);
            // 是否禁止跳岗标识
            camundaJumpPoint.setNoJumpFlag(CamundaConst.STR_0);
            // 版本信息
            camundaJumpPoint.setTemplateVersion(actReProcdef.getVersion().toString());
            // 插入信息
            camundaJumpPointMapper.insert(camundaJumpPoint);
        });
    }

    /**
     * 操作跳跃节点信息
     *
     * @param camundaJumpPointSetDTO 参数dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void optionJumpPoint(CamundaJumpPointSetDTO camundaJumpPointSetDTO) {
        // 跳跃节点不能包含当前节点信息
        if (ObjectUtil.isNotEmpty(camundaJumpPointSetDTO.getJumpPoints())
                && camundaJumpPointSetDTO.getJumpPoints().contains(camundaJumpPointSetDTO.getCurrentActivityId())) {
            throw new ServiceException("跳跃节点不能包含当前节点信息");
        }

        // 通过流程定义id获取流程定义信息
        String processDefinitionId = camundaJumpPointSetDTO.getProcessDefinitionId();
        ActReProcdef actReProcdef = actReProcdefMapper.selectById(processDefinitionId);
        if (ObjectUtil.isNull(actReProcdef)) {
            throw new ServiceException(StrUtil.format("通过processDefinitionId[{}]查询流程定义实例信息为空", processDefinitionId));
        }

        // 获取流程名称以及定义key以及version信息
        String processTemplateName = actReProcdef.getName();
        String processTemplateKey = actReProcdef.getKey();
        String version = actReProcdef.getVersion().toString();

        // 查询预设置跳岗信息
        QueryWrapper<CamundaJumpPoint> camundaJumpPointQueryWrapper = new QueryWrapper<>();
        camundaJumpPointQueryWrapper.lambda().eq(CamundaJumpPoint::getActivityId, camundaJumpPointSetDTO.getCurrentActivityId())
                .eq(CamundaJumpPoint::getProcessTemplateKey, processTemplateKey)
                .eq(CamundaJumpPoint::getProcessTemplateName, processTemplateName)
                .eq(CamundaJumpPoint::getTemplateVersion, version)
                .eq(CamundaJumpPoint::getOwnOrg, camundaJumpPointSetDTO.getOrgCd());
        CamundaJumpPoint camundaJumpPoint = camundaJumpPointMapper.selectOne(camundaJumpPointQueryWrapper);
        if (ObjectUtil.isNull(camundaJumpPoint)) {
            throw new ServiceException(StrUtil.format("获取设置跳岗节点信息为空!"));
        }

        // 开始设置节点信息
        if (StrUtil.isNotBlank(camundaJumpPointSetDTO.getJumpPoints())) {
            camundaJumpPoint.setJumpActivityIds(camundaJumpPointSetDTO.getJumpPoints());
            camundaJumpPointMapper.updateById(camundaJumpPoint);
        } else {
            // 跳转节点为空,则设置不能跳转
            camundaJumpPointMapper.deleteJumpPointsById(camundaJumpPoint.getId());
        }
    }

    /**
     * 获取全部流程定义列表信息
     *
     * @param camundaDeployDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<ActReProcdef> getReProcessDeployPage(CamundaDeployDTO camundaDeployDTO) {
        // 进行信息查询
        QueryWrapper<ActReProcdef> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(StrUtil.isNotBlank(camundaDeployDTO.getProcessTemplateKey()), "KEY_", camundaDeployDTO.getProcessTemplateKey())
                .like(StrUtil.isNotBlank(camundaDeployDTO.getProcessTemplateName()), "NAME_", camundaDeployDTO.getProcessTemplateName())
                .eq(StrUtil.isNotBlank(camundaDeployDTO.getVersion()), "VERSION_", camundaDeployDTO.getVersion());
        List<ActReProcdef> actReProcdefList = actReProcdefMapper.selectList(queryWrapper);
        return pageUtil.setFlowListPage(actReProcdefList, PaginationContext.getPageNum(), PaginationContext.getPageSize());
    }

    /**
     * 获取任务节点信息
     *
     * @param camundaJumpPointPageDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<CamundaJumpPoint> getJumpPointPage(CamundaJumpPointPageDTO camundaJumpPointPageDTO) {
        // 获取流程定义信息
        ActReProcdef actReProcdef = actReProcdefMapper.selectById(camundaJumpPointPageDTO.getProcessDefinitionId());
        if (ObjectUtil.isNull(actReProcdef)) {
            throw new ServiceException(StrUtil.format("通过processDefinitionId[{}]查询流程定义实例信息为空", camundaJumpPointPageDTO.getProcessDefinitionId()));
        }

        // 获取流程名称以及定义key以及version信息
        String processTemplateName = actReProcdef.getName();
        String processTemplateKey = actReProcdef.getKey();
        String version = actReProcdef.getVersion().toString();

        // 查询预设置跳岗信息
        List<String> unExpectActivityIdList = camundaJumpPointPageDTO.getUnExpectActivityIdList();
        QueryWrapper<CamundaJumpPoint> camundaJumpPointQueryWrapper = new QueryWrapper<>();
        camundaJumpPointQueryWrapper.lambda()
                .eq(CamundaJumpPoint::getProcessTemplateKey, processTemplateKey)
                .eq(CamundaJumpPoint::getProcessTemplateName, processTemplateName)
                .eq(CamundaJumpPoint::getTemplateVersion, version)
                // 跳跃节点设置权限失效标识 0 生效, 1 失效
                .eq(CamundaJumpPoint::getIsDel, CamundaConst.STR_0)
                .eq(CamundaJumpPoint::getOwnOrg, camundaJumpPointPageDTO.getOrgCd())
                // 排除自定义无用节点
                .notIn(ObjectUtil.isNotEmpty(unExpectActivityIdList), CamundaJumpPoint::getActivityId, unExpectActivityIdList);
        List<CamundaJumpPoint> camundaJumpPointList = camundaJumpPointMapper.selectList(camundaJumpPointQueryWrapper);

        return pageUtil.setFlowListPage(camundaJumpPointList, PaginationContext.getPageNum(), PaginationContext.getPageSize());
    }

    /**
     * 获取跟踪列表信息
     * 以任务角度出发,可能会出现一个businessKey对应多个task任务,因为此人在流程中多次审批,以不同岗位
     *
     * @param camundaTraceHistoryDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<List<CamundaTraceHistory>> getCamundaTracePage(CamundaTraceHistoryDTO camundaTraceHistoryDTO) {
        // 封装列表参数信息
        List<CamundaTraceHistory> camundaTraceHistoryList = new ArrayList<>();

        // 封装查询参数信息 userCd:orgCd
        String handleUser = camundaTraceHistoryDTO.getUserCd()
                .concat(StrUtil.COLON)
                .concat(camundaTraceHistoryDTO.getOrgCd());

        // 封装查询参数信息
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .tenantIdIn(camundaTraceHistoryDTO.getCamundaTenantEnum().getTenantId())
                .taskInvolvedUser(handleUser)
                .orderByProcessInstanceId()
                .desc()
                .matchVariableNamesIgnoreCase();

        // 条件参数确认,任务模板查询
        if (StrUtil.isNotBlank(camundaTraceHistoryDTO.getProcessTemplateKey())) {
            historicTaskInstanceQuery.processVariableValueEquals(CamundaConst.PROCESS_TEMPLATE_KEY, camundaTraceHistoryDTO.getProcessTemplateKey());
        }
        // 客户名称查询
        if (StrUtil.isNotBlank(camundaTraceHistoryDTO.getCustomerName())) {
            historicTaskInstanceQuery.processVariableValueEquals(CamundaConst.CUSTOMER_NAME_FLAG, camundaTraceHistoryDTO.getCustomerName());
        }
        // 发起业务系统
        if (StrUtil.isNotBlank(camundaTraceHistoryDTO.getSystem())) {
            historicTaskInstanceQuery.processVariableValueEquals(CamundaConst.SYSTEM, camundaTraceHistoryDTO.getSystem());
        }

        // 流程在途结束判定
        if (CamundaConst.RUNNING.equals(camundaTraceHistoryDTO.getStatus())) {
            // 在途任务查询
            historicTaskInstanceQuery.processUnfinished();
        } else if (CamundaConst.FINISHED.equals(camundaTraceHistoryDTO.getStatus())) {
            // 已经结束任务查询参数封装
            historicTaskInstanceQuery.processFinished();
        } else {
            throw new ServiceException("查询跟踪列信息,是否在途参数获取为空!");
        }

        // 获取所有任务列表信息
        List<HistoricTaskInstance> historicTaskInstanceList = historicTaskInstanceQuery.list();

        // 进行数据封装
        int totalCount = 0;
        if (ObjectUtil.isNotEmpty(historicTaskInstanceList)) {
            // 查询历史记录参数信息为map信息
            Map<String, Map<String, Object>> historyVariablesMap = new HashMap<>(CamundaConst.INT_16);
            Set<String> processIdList = historicTaskInstanceList.stream().
                    map(HistoricTaskInstance::getProcessInstanceId)
                    .collect(Collectors.toSet());

            // 获取processInstance实例列表信息
            int firstResultNum = pageUtil.getStartSizeNum(PaginationContext.getPageNum(), PaginationContext.getPageSize());
            HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceIds(processIdList)
                    .orderByProcessInstanceStartTime()
                    .desc()
                    .matchVariableValuesIgnoreCase();

            // 流程实例历史信息进行分页查询
            totalCount = (int)processInstanceQuery.count();
            List<HistoricProcessInstance> historicProcessInstanceList = processInstanceQuery
                    .listPage(firstResultNum, PaginationContext.getPageSize());

            // 组装查询variables 参数信息idArray
            String[] processIdArray = processIdList.toArray(new String[processIdList.size()]);
            List<HistoricVariableInstance> variableInstanceList = historyService.createHistoricVariableInstanceQuery()
                    // 租户信息
                    .tenantIdIn(camundaTraceHistoryDTO.getCamundaTenantEnum().getTenantId())
                    // 流程实例id列表
                    .processInstanceIdIn(processIdArray)
                    .list();

            // 开始封装参数map key 为processInstanceId,value为当前processInstanceId下个属性map信息
            if (ObjectUtil.isNotEmpty(variableInstanceList)) {
                variableInstanceList.forEach(variableInstance-> {
                    String processInstanceId = variableInstance.getProcessInstanceId();

                    // 当前那processInstanceId加入过map
                    Map<String, Object> currentMap;
                    if (ObjectUtil.isNotEmpty(historyVariablesMap.get(processInstanceId))) {
                        currentMap = historyVariablesMap.get(processInstanceId);
                    } else {
                        // 首次加入map信息
                        currentMap = new HashMap<>(CamundaConst.INT_16);
                    }
                    currentMap.put(variableInstance.getName(), variableInstance.getValue());
                    historyVariablesMap.put(processInstanceId, currentMap);
                });
            }

            historicProcessInstanceList.forEach(processInstance -> {
                // 获取参数信息
                Map<String, Object> variablesMap = historyVariablesMap.get(processInstance.getId());
                CamundaTraceHistory camundaTraceHistory = new CamundaTraceHistory();
                camundaTraceHistory.setBizType(StrUtil.toString(variablesMap.get(CamundaConst.PROCESS_BIZ_TYPE)));
                camundaTraceHistory.setBusinessKey(StrUtil.toString(variablesMap.get(CamundaConst.BUSINESS_KEY_FLAG)));
                camundaTraceHistory.setCreator(StrUtil.toString(variablesMap.get(CamundaConst.PROCESS_CREATOR)));
                camundaTraceHistory.setOrgCd(StrUtil.toString(variablesMap.get(CamundaConst.PROCESS_CREATOR)));
                camundaTraceHistory.setCustomerName(StrUtil.toString(variablesMap.get(CamundaConst.CUSTOMER_NAME_FLAG)));
                camundaTraceHistory.setCustomerNum(StrUtil.toString(variablesMap.get(CamundaConst.CUSTOMER_NAME_FLAG)));
                camundaTraceHistory.setProcessId(processInstance.getId());
                camundaTraceHistory.setCreateDate(processInstance.getStartTime());
                if (CamundaConst.FINISHED.equals(camundaTraceHistoryDTO.getStatus())) {
                    camundaTraceHistory.setStatus(CamundaConst.FINISHED);
                } else {
                    camundaTraceHistory.setStatus(CamundaConst.RUNNING);
                }

                camundaTraceHistoryList.add(camundaTraceHistory);
            });
        }

        // 返回分页信息
        return pageUtil.setCountFlowListPage(camundaTraceHistoryList,
                PaginationContext.getPageNum(),
                PaginationContext.getPageSize(),
                totalCount);
    }

    /**
     * 跟踪列表处理人列表信息
     *
     * @param camundaTraceApproveHistoryDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<CamundaTraceApproveHistory> getCamundaTraceApprovePage(CamundaTraceApproveHistoryDTO camundaTraceApproveHistoryDTO) {
        // 获取流程实例id
        String processInstanceId = camundaTraceApproveHistoryDTO.getProcessId();

        // 获取历史任务查询类
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                // 租户信息
                .tenantIdIn(camundaTraceApproveHistoryDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime()
                .desc()
                .matchVariableNamesIgnoreCase();

        // 分页查询
        int totalCount = (int) historicTaskInstanceQuery.count();
        int firstResultNum = pageUtil.getStartSizeNum(PaginationContext.getPageNum(), PaginationContext.getPageSize());
        List<HistoricTaskInstance> historicTaskInstanceList = historicTaskInstanceQuery.listPage(firstResultNum, PaginationContext.getPageSize());

        // 最终返回分页信息
        List<CamundaTraceApproveHistory> camundaTraceApproveHistoryList = new ArrayList<>();

        // 进行数据封装
        if (ObjectUtil.isNotEmpty(historicTaskInstanceList)) {
            // 查询历史记录参数信息为map信息
            Map<String, Object> historyVariablesMap = new HashMap<>(CamundaConst.INT_16);

            // 组装查询variables
            List<HistoricVariableInstance> variableInstanceList = historyService.createHistoricVariableInstanceQuery()
                    // 租户信息
                    .tenantIdIn(camundaTraceApproveHistoryDTO.getCamundaTenantEnum().getTenantId())
                    // 流程实例id
                    .processInstanceIdIn(processInstanceId)
                    .list();

            // 开始封装参数map key 为processInstanceId,value为当前 processInstanceId 下个属性map信息
            if (ObjectUtil.isNotEmpty(variableInstanceList)) {
                variableInstanceList.forEach(variableInstance -> {
                    historyVariablesMap.put(variableInstance.getName(), variableInstance.getValue());
                });
            }

            historicTaskInstanceList.forEach(historicTaskInstance -> {
                // 获取参数信息
                CamundaTraceApproveHistory camundaTraceApproveHistory = new CamundaTraceApproveHistory();
                // 业务编号
                camundaTraceApproveHistory.setBusinessKey(StrUtil.toString(historyVariablesMap.get(CamundaConst.BUSINESS_KEY_FLAG)));
                // 流程实例id
                camundaTraceApproveHistory.setProcessId(historicTaskInstance.getProcessInstanceId());
                // 创建时间
                camundaTraceApproveHistory.setCreateDate(historicTaskInstance.getStartTime());
                // 结束时间
                camundaTraceApproveHistory.setEndDate(historicTaskInstance.getEndTime());
                // 操作岗位名称
                camundaTraceApproveHistory.setPositionName(historicTaskInstance.getName());
                // 机构信息 assign 截取
                camundaTraceApproveHistory.setOrgCd(historicTaskInstance.getAssignee());
                // 柜员信息 assign 截取
                camundaTraceApproveHistory.setUserCd(historicTaskInstance.getAssignee());
                // 获取评论信息
                String commentMessage = null;
                String taskId = historicTaskInstance.getId();
                List<Comment> commentList = taskService.getTaskComments(taskId);
                if (ObjectUtil.isNotEmpty(commentList)) {
                    commentMessage = commentList.get(CamundaConst.INT_0).getFullMessage();
                }
                camundaTraceApproveHistory.setOpinion(commentMessage);

                camundaTraceApproveHistoryList.add(camundaTraceApproveHistory);
            });
        }
        return pageUtil.setCountFlowListPage(camundaTraceApproveHistoryList,
                PaginationContext.getPageNum(),
                PaginationContext.getPageSize(),
                totalCount);
    }

    /**
     * 获取已经部署流程模板列表信息
     *
     * @param camundaLoadTemplateDTO 参数dto
     * @return
     */
    @Override
    public CamundaFlowPage<CamundaLoadTemplate> getLoadTemplatePage(CamundaLoadTemplateDTO camundaLoadTemplateDTO) {
        // 定义查询类
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        // 设置爱租户信息
        processDefinitionQuery.tenantIdIn(camundaLoadTemplateDTO.getCamundaTenantEnum().getTenantId());
        // 流程定义条件查询,分别是定义key,以及流程定义名称还有版本号
        if (StrUtil.isNotBlank(camundaLoadTemplateDTO.getTemplateKey())) {
            processDefinitionQuery.processDefinitionKeyLike(camundaLoadTemplateDTO.getTemplateKey());
        }
        // 版本编号如果为空,则默认设置为最终版本
        if (ObjectUtil.isNotNull(camundaLoadTemplateDTO.getVersion())) {
            processDefinitionQuery.processDefinitionVersion(camundaLoadTemplateDTO.getVersion());
        } else {
            processDefinitionQuery.latestVersion();
        }

        // 开始查询列表信息
        List<ProcessDefinition> definitionList = processDefinitionQuery
                .orderByProcessDefinitionKey()
                .asc()
                .list();
        // 封装返回分页信息
        List<CamundaLoadTemplate> camundaLoadTemplateList = new ArrayList<>();
        if (ObjectUtil.isNotEmpty(definitionList)) {
            camundaLoadTemplateList = definitionList.stream().filter(definition -> {
                if (StrUtil.isBlank(camundaLoadTemplateDTO.getTemplateName())) {
                    return true;
                } else if (ObjectUtil.isNull(definition) || StrUtil.isBlank(definition.getName())) {
                    return false;
                } else {
                    return definition.getName().contains(camundaLoadTemplateDTO.getTemplateName());
                }
            }).map(definition -> {
                CamundaLoadTemplate camundaLoadTemplate = new CamundaLoadTemplate();
                camundaLoadTemplate.setId(definition.getDeploymentId());
                camundaLoadTemplate.setTemplateKey(definition.getKey());
                camundaLoadTemplate.setTemplateName(definition.getName());
                camundaLoadTemplate.setVersion(definition.getVersion());
                // 通过deploymentId获取deployment部署时间信息
                String deploymentId = definition.getDeploymentId();
                Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId)
                        .singleResult();
                if (ObjectUtil.isNotNull(deployment)) {
                    camundaLoadTemplate.setCreateTime(DateUtil.format(deployment.getDeploymentTime(), DatePattern.NORM_DATETIME_PATTERN));
                }
                return camundaLoadTemplate;
            }).collect(Collectors.toList());
        }

        return pageUtil.setCountFlowListPage(camundaLoadTemplateList,
                PaginationContext.getPageNum(),
                PaginationContext.getPageSize(),
                definitionList.size());
    }

    /**
     * 任务驳回到发起岗或者上一岗位
     *
     * @param camundaRejectTaskDTO 驳回参数DTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean rejectTaskToStartNode(CamundaRejectTaskDTO camundaRejectTaskDTO) {
        // 获取当前任务信息
        Task currentTask = taskService.createTaskQuery()
                // 租户信息
                .tenantIdIn(camundaRejectTaskDTO.getCamundaTenantEnum().getTenantId())
                .taskId(camundaRejectTaskDTO.getTaskId())
                .singleResult();
        if (ObjectUtil.isNull(currentTask)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]获取当前任务信息为空!", camundaRejectTaskDTO.getTaskId()));
        }

        // 通过task任务信息获取流程实例信息
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                // 租户信息
                .tenantIdIn(camundaRejectTaskDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(currentTask.getProcessInstanceId())
                .singleResult();
        if (ObjectUtil.isNull(processInstance)) {
            throw new ServiceException(StrUtil.format("通过processId[{}]获取流程实例信息为空!", processInstance.getProcessInstanceId()));
        }

        // 获取已经结束的历史节点信息
        List<HistoricActivityInstance> activityInstanceList = historyService.createHistoricActivityInstanceQuery()
                // 租户信息
                .tenantIdIn(camundaRejectTaskDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(processInstance.getProcessInstanceId())
                // 节点类型
                .activityType(CamundaConst.USER_TASK)
                // 已经结束的
                .finished()
                // 按照结束时间进行升序排序
                .orderByHistoricActivityInstanceEndTime()
                .asc()
                .list();

        // 空值校验
        if (ObjectUtil.isEmpty(activityInstanceList)) {
            throw new ServiceException("获取历史userTask任务信息为空!");
        }

        // 首个任务节点
        HistoricActivityInstance startInstance = activityInstanceList.get(CamundaConst.INT_0);

        // 设置退回操作员信息以及退回节点activityId信息
        String activityId = null;
        String assignee = null;

        // 强制退回到发起节点判定
        if (camundaRejectTaskDTO.getIfRejectToStartNode()) {
            // 如果历史userTask节点没有或者只有一个,则不能进行驳回操作
            if (activityInstanceList.size() < CamundaConst.INT_1) {
                throw new ServiceException("首个用户操作节点无法驳回!");
            }

            // 获取跳转节点信息,目标节点id以及节点操作员信息
            activityId = startInstance.getActivityId();
            assignee = startInstance.getAssignee();
        } else {
            // 退回到上一节点判定,当前节点为首个节点,无法退回
            if (currentTask.getTaskDefinitionKey().equals(startInstance.getActivityId())) {
                throw new ServiceException("首个用户操作节点无法驳回!");
            }

            // 判断当前任务是否已经退回过
            List<HistoricActivityInstance> equalCurrentActivityList = activityInstanceList.stream().filter(instance -> {
                if (instance.getActivityId().equals(currentTask.getTaskDefinitionKey())) {
                    return true;
                }
                return false;
            }).collect(Collectors.toList());

            // 首次退回
            if (ObjectUtil.isEmpty(equalCurrentActivityList)) {
                // 倒数第一个为上一岗位信息
                HistoricActivityInstance historicActivityInstance = activityInstanceList.get(activityInstanceList.size() - 1);
                activityId = historicActivityInstance.getActivityId();
                assignee = historicActivityInstance.getAssignee();
            } else {
                // 经过退回操作,eg: 1 2 3 4,4个节点当前节点信息如果在3,则表示该节点是从4退回来的,如果再退回则需要退回到2,再到1
                for (int index = 0; index < activityInstanceList.size(); index ++) {
                    // 获取循环对象
                    HistoricActivityInstance loopActivityInstance = activityInstanceList.get(index);
                    // 如果当前循环activity信息不是第一节点,并且循环节点等于当前任务节点,则上一节点就为应退回节点
                    if (index > CamundaConst.INT_0
                            && loopActivityInstance.getActivityId().equals(currentTask.getTaskDefinitionKey())) {
                        HistoricActivityInstance backActivityInstance = activityInstanceList.get(index - CamundaConst.INT_1);
                        activityId = backActivityInstance.getActivityId();
                        assignee = backActivityInstance.getAssignee();
                    }
                }
            }
        }

        // 设置参数以及拒绝任务的原因参数信息
        Map<String, Object> variablesMap = new HashMap<>();
        variablesMap.put(CamundaConst.APPROVE, assignee);
        taskService.createComment(currentTask.getId(), processInstance.getProcessInstanceId(), camundaRejectTaskDTO.getRejectReason());

        // 获取流程定义信息,获取结束任务对应节点,如果强制结束则直接获取流程中end结束节点进行结束任务
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
                .getProcessDefinition(processInstance.getProcessDefinitionId());

        // 获取当前节点
        ActivityImpl currentActivity = processDefinition.findActivity(currentTask.getTaskDefinitionKey());

        runtimeService.createProcessInstanceModification(processInstance.getProcessInstanceId())
                // 驳回原因
                .setAnnotation(camundaRejectTaskDTO.getRejectReason())
                // 取消当前活动的所有任务
                .cancelAllForActivity(currentActivity.getActivityId())
                // 目标节点Id,在流程图中看
                .startBeforeActivity(activityId)
                // 参数信息
                .setVariables(variablesMap)
                .execute();

        return true;
    }

    /**
     * 跳转到目标节点
     *
     * @param camundaGotoDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean gotoDestinationTask(CamundaGotoDTO camundaGotoDTO) {
        // 获取当前节点task任务信息
        Task currentTask = taskService.createTaskQuery()
                // 租户信息
                .tenantIdIn(camundaGotoDTO.getCamundaTenantEnum().getTenantId())
                // 任务节点信息
                .taskId(camundaGotoDTO.getCurrentTaskId()).singleResult();
        if (ObjectUtil.isNull(currentTask)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]获取task信息为空!", camundaGotoDTO.getCurrentTaskId()));
        }

        // 校验节点是否可以跳跃,获取流程定义id,当前节点activityId以及机构信息
        String processDefinitionId = currentTask.getProcessDefinitionId();
        String currentActivityId = currentTask.getTaskDefinitionKey();
        String ownOrgCd = camundaGotoDTO.getOrgCd();

        // 查询当前节点是否配置可跳跃节点信息
        ActReProcdef actReProcdef = actReProcdefMapper.selectById(processDefinitionId);
        if (ObjectUtil.isNull(actReProcdef)) {
            throw new ServiceException(StrUtil.format("通过流程定义id[{}],获取流程定义信息为空!", processDefinitionId));
        }

        // 获取当前节点配置信息
        QueryWrapper<CamundaJumpPoint> jumpPointQueryWrapper = new QueryWrapper<>();
        jumpPointQueryWrapper.lambda().eq(CamundaJumpPoint::getActivityId, currentActivityId)
                .eq(CamundaJumpPoint::getTemplateVersion, actReProcdef.getVersion())
                .eq(CamundaJumpPoint::getOwnOrg, ownOrgCd)
                .eq(CamundaJumpPoint::getProcessTemplateKey, actReProcdef.getKey());
        CamundaJumpPoint camundaJumpPoint = camundaJumpPointMapper.selectOne(jumpPointQueryWrapper);
        if (ObjectUtil.isNull(camundaJumpPoint)) {
            throw new ServiceException(StrUtil.format("当前模板[{}]对应机构[{}]对应岗位未配置跳岗信息,请确认!",
                    actReProcdef.getKey(),
                    ownOrgCd));
        }

        // 岗位配置正确性校验,可跳跃节点不好含当前跳跃节点,则直接返回异常信息
        if (StrUtil.isNotBlank(camundaJumpPoint.getJumpActivityIds())
                && !camundaJumpPoint.getJumpActivityIds().contains(camundaGotoDTO.getDestActivityId())) {
            throw new ServiceException("当前岗位不能跳转到选定岗位,请配置后再试!");
        }

        // 设置意见信息
        taskService.createComment(currentTask.getId(), currentTask.getProcessInstanceId(), camundaGotoDTO.getOpinion());

        // 进行跳岗信息
        ProcessInstanceModificationInstantiationBuilder instantiationBuilder = runtimeService
                .createProcessInstanceModification(currentTask.getProcessInstanceId())
                // 取消当前活动的所有任务
                .cancelAllForActivity(currentActivityId)
                // 跳转原因
                .setAnnotation(camundaGotoDTO.getOpinion())
                // 目标节点Id,在流程图中看
                .startBeforeActivity(camundaGotoDTO.getDestActivityId());

        // 参数信息
        if (ObjectUtil.isNotEmpty(camundaGotoDTO.getVariablesMap())) {
            instantiationBuilder.setVariables(camundaGotoDTO.getVariablesMap());
        }

        // 执行节点跳跃
        instantiationBuilder.execute();

        return true;
    }

    /**
     * 获取当前任务完成节点,连线,未完成节点信息
     * todo: 后续可以加入自定义的完成节点,退回操作记录表,更准确展示当前节点任务高亮信息
     *
     * @param camundaTaskImageDTO 参数dto
     * @return
     */
    @Override
    public CamundaTaskImageNodeRspDTO getCamundaTaskImage(CamundaTaskImageDTO camundaTaskImageDTO) {
        // 最终返回参数信息
        CamundaTaskImageNodeRspDTO camundaTaskImageNodeRspDTO = new CamundaTaskImageNodeRspDTO();

        // 根据taskId获取任务信息
        String taskId = camundaTaskImageDTO.getTaskId();
        Task currentTask = taskService.createTaskQuery()
                .tenantIdIn(camundaTaskImageDTO.getCamundaTenantEnum().getTenantId())
                .taskId(taskId)
                .singleResult();
        if (ObjectUtil.isNull(currentTask)) {
            throw new ServiceException(StrUtil.format("通过taskId[{}]以及租户id[{}]获取task信息为空!",
                    taskId,
                    camundaTaskImageDTO.getCamundaTenantEnum().getTenantId()));
        }

        // 获取processInstance信息
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                // 租户id
                .tenantIdIn(camundaTaskImageDTO.getCamundaTenantEnum().getTenantId())
                // 流程实例id
                .processInstanceId(currentTask.getProcessInstanceId())
                .singleResult();
        if (ObjectUtil.isNull(processInstance)) {
            throw new ServiceException(StrUtil.format("通过processId[{}]获取流程实例信息为空!", currentTask.getProcessInstanceId()));
        }

        // 流程实例id
        String processInstanceId = processInstance.getProcessInstanceId();

        // 获取已经完成的任务节点
        List<HistoricActivityInstance> finishedActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                .tenantIdIn(camundaTaskImageDTO.getCamundaTenantEnum().getTenantId())
                .processInstanceId(processInstanceId)
                .finished()
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();
        if (ObjectUtil.isNotEmpty(finishedActivityInstanceList)) {
            List<String> finishedActivityIdList = finishedActivityInstanceList
                    .stream()
                    .map(HistoricActivityInstance::getActivityId)
                    .collect(Collectors.toList());
            camundaTaskImageNodeRspDTO.setFinishedActivityIdList(finishedActivityIdList);
        }

        // 未完成节点信息
        List<HistoricActivityInstance> unfinishedActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                .tenantIdIn(camundaTaskImageDTO.getCamundaTenantEnum().getTenantId())
                .processInstanceId(processInstanceId)
                .unfinished()
                .list();
        if (ObjectUtil.isNotEmpty(unfinishedActivityInstanceList)) {
            List<String> unfinishedActivityIdList = unfinishedActivityInstanceList
                    .stream()
                    .map(HistoricActivityInstance::getActivityId)
                    .collect(Collectors.toList());
            camundaTaskImageNodeRspDTO.setUnfinishedActivityIdList(unfinishedActivityIdList);
        }

        // 自己办理任务获取
        String optionUser = camundaTaskImageDTO.getUserCd().concat(StrUtil.COLON).concat(camundaTaskImageDTO.getOrgCd());
        List<HistoricTaskInstance> selfOptionTaskInstanceList = historyService.createHistoricTaskInstanceQuery()
                .tenantIdIn(camundaTaskImageDTO.getCamundaTenantEnum().getTenantId())
                .taskAssignee(optionUser)
                .finished()
                .processInstanceId(processInstanceId)
                .list();
        if (ObjectUtil.isNotEmpty(selfOptionTaskInstanceList)) {
            List<String> selfOptionIdList = selfOptionTaskInstanceList
                    .stream()
                    .map(HistoricTaskInstance::getTaskDefinitionKey)
                    .collect(Collectors.toList());
            camundaTaskImageNodeRspDTO.setSelfOptionActivityIdList(selfOptionIdList);
        }

        // 获取流程中的高亮线,获取流程定义的bpmn模型
        List<String> highLineList = new ArrayList<>();
        BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(processInstance.getProcessDefinitionId());
        for (HistoricActivityInstance finishedActivity : finishedActivityInstanceList) {
            // 获取 bpmn 元素
            ModelElementInstance modelElementInstance = bpmnModelInstance.getModelElementById(finishedActivity.getActivityId());

            // 将节点转换为 flowNode 流程节点,获取输入输出线
            FlowNode flowNode = (FlowNode)modelElementInstance;

            // 获取outgoing线信息
            Collection<SequenceFlow> outgoingCollection = flowNode.getOutgoing();
            if (ObjectUtil.isEmpty(outgoingCollection)) {
                continue;
            }

            // 循环outgoing信息
            outgoingCollection.forEach(outgoing -> {
                // 线段目标节点信息
                String targetId = outgoing.getTarget().getId();

                // 完成任务,多次循环,所有完成任务对应outgoing线全部高亮
                for (HistoricActivityInstance innerFinished : finishedActivityInstanceList) {
                    String optionActivityId = innerFinished.getActivityId();
                    // 循环任务为target目标节点,并且 则高亮
                    if(targetId.equals(optionActivityId)){
                        if(finishedActivity.getEndTime().equals(innerFinished.getStartTime())){
                            highLineList.add(outgoing.getId());
                        }
                    }
                }

                // 待完成任务高亮节点
                for (HistoricActivityInstance unfinishedActivityInstance : unfinishedActivityInstanceList) {
                    String optionActivityId = unfinishedActivityInstance.getActivityId();
                    if(targetId.equals(optionActivityId)){
                        if(finishedActivity.getEndTime().equals(unfinishedActivityInstance.getStartTime())){
                            highLineList.add(outgoing.getId());
                        }
                    }
                }
            });
            camundaTaskImageNodeRspDTO.setHighLineList(highLineList);
        }

        return camundaTaskImageNodeRspDTO;
    }

    /**
     * 通过版本号,流程定义processDefinitionKey获取流程定义xml信息
     *
     * @param processDefinitionKey
     * @param version 版本编号
     * @param tenantEnum 租户枚举信息
     */
    @Override
    public String getDefinitionXMLByKeyAndVersion(String processDefinitionKey, Integer version, CamundaTenantEnum tenantEnum) throws IOException {
        // 获取流程定义信息
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery()
                // 租户id
                .tenantIdIn(tenantEnum.getTenantId())
                .processDefinitionKey(processDefinitionKey)
                .list();

        // 空值判定
        if (ObjectUtil.isEmpty(processDefinitionList)) {
            throw new ServiceException(StrUtil.format("通过processDefinitionKey[{}]获取流程定义信息为空!",
                    processDefinitionKey));
        }
        // 获取对应版本的信息
        ProcessDefinition processDefinition = processDefinitionList.stream().filter(definition ->
                version.equals(definition.getVersion())
        ).findFirst().orElse(null);

        // 空值判定
        if (ObjectUtil.isNull(processDefinition)) {
            throw new ServiceException(StrUtil.format("通过processDefinitionKey[{}]获取版本号[{}]的流程定义信息为空,未找到对应版本定义信息!",
                    processDefinitionKey,
                    version));
        }

        // 获取文件输入流信息
        InputStream xmlStream = repositoryService.getProcessModel(processDefinition.getId());

        // 通过 inputStream 获取xml字符串信息
        String xmlStr = IOUtils.toString(xmlStream, StandardCharsets.UTF_8);

        return xmlStr;
    }

    /**
     * 获取流程图
     *
     * @param processId 流程实例id
     */
    @Override
    public void getDefinitionXMLByProcessId(String processId, CamundaTenantEnum camundaTenantEnum) throws IOException {
        // 通过runtimeService获取processDefinitionId
        String processDefinitionId = runtimeService.createProcessInstanceQuery()
                // 租户信息
                .tenantIdIn(camundaTenantEnum.getTenantId())
                .processInstanceId(processId)
                .singleResult()
                .getProcessDefinitionId();

        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                // 租户信息
                .tenantIdIn(camundaTenantEnum.getTenantId())
                .processDefinitionId(processDefinitionId)
                .singleResult();

        InputStream xmlStream = repositoryService.getProcessModel(processDefinition.getId());
        // 设置响应头信息,比如Content-Type和Content-Disposition
        response.setContentType("application/xml");
        // 根据实际文件类型设置MIME类型,这里是XML文件
        response.setHeader("Content-Disposition", "attachment;filename=\"" + processDefinition.getKey() + "\"");
        try (OutputStream out = response.getOutputStream()) {
            byte[] buffer = new byte[1024]; // 缓冲区大小,可以根据实际情况调整
            int bytesRead;

            // 从xmlStream读取数据并写入到responseOutputStream
            while ((bytesRead = xmlStream.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            // 处理IO异常
            String errorMessage = LogUtil.getStackTraceInfo(e);
            throw new ServiceException(StrUtil.format("下载文件异常,异常信息为[{}]", errorMessage));
        } finally {
            response.flushBuffer();
        }
    }

    /**
     * 获取流程图XML字符串
     *
     * @param processId 流程实例id
     * @param camundaTenantEnum 租户枚举信息
     */
    @Override
    public String getDefinitionXMLStrByProcessId(String processId, CamundaTenantEnum camundaTenantEnum) throws IOException {
        // 通过runtimeService获取processDefinitionId
        String processDefinitionId = runtimeService.createProcessInstanceQuery()
                // 租户信息
                .tenantIdIn(camundaTenantEnum.getTenantId())
                .processInstanceId(processId)
                .singleResult()
                .getProcessDefinitionId();
        // 空值判定
        if (StrUtil.isBlank(processDefinitionId)) {
            throw new ServiceException("通过processId获取流程定义processDefinitionId为空!");
        }
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .tenantIdIn(camundaTenantEnum.getTenantId())
                .processDefinitionId(processDefinitionId)
                .singleResult();

        // 空值判定
        if (ObjectUtil.isNull(processDefinition)) {
            throw new ServiceException(StrUtil.format("通过processDefinitionId[{}]获取流程定义信息为空!",
                    processDefinitionId));
        }
        InputStream xmlStream = repositoryService.getProcessModel(processDefinition.getId());

        // 通过 inputStream 获取xml字符串信息
        String xmlStr = IOUtils.toString(xmlStream, StandardCharsets.UTF_8);

        return xmlStr;
    }

    /**
     * 部署流程
     *
     * @param file 文件
     * @param tenantEnum 租户枚举
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String deployProcess(MultipartFile file, CamundaTenantEnum tenantEnum) throws IOException {
        // 文件校验
        if (file.isEmpty()) {
            throw new ServiceException("部署新流程,流程文件为空!");
        }
        // 开始部署流程
        Deployment deployment = repositoryService.createDeployment()
                .addInputStream(file.getOriginalFilename(), file.getInputStream())
                .tenantId(tenantEnum.getTenantId())
                .name(file.getOriginalFilename())
                .deploy();

        // 通过deploymentId获取流程定义集合信息(首次部署,只有一个流程定义信息)
        String deploymentId = deployment.getId();
        List<ProcessDefinition> definitionList = repositoryService.createProcessDefinitionQuery()
                // 租户id
                .tenantIdIn(tenantEnum.getTenantId())
                .deploymentId(deploymentId)
                .list();

        // 通过流程定义信息获取流程定义key
        if (ObjectUtil.isNotEmpty(definitionList)) {
            // 首次部署,取第一个定义信息即可
            ProcessDefinition processDefinition = definitionList.get(0);
            return processDefinition.getKey();
        } else {
            throw new ServiceException(StrUtil.format("流程已经部署,通过部署deploymentId[{}]获取流程定义信息为空!",
                    deploymentId));
        }
    }

    /**
     * 获取排他网关正确的下一节点信息
     * @param currentActivity 当前节点信息
     * @param currentVariablesMap 当前节点处参数信息
     * @return
     */
    public List<PvmActivity> getNextPositionByExclusiveGateway(PvmActivity currentActivity, Map<String, Object> currentVariablesMap) {
        // 返回结果信息
        List<PvmActivity> activityList = new ArrayList<>();

        // 获取当前节点对外连接信息,并且计算最终选择哪条路线
        List<PvmTransition> transitionList = currentActivity.getOutgoingTransitions();
        for (PvmTransition transition : transitionList) {
            // 获取当前任务判定条件,注意此处不是节点,为对外条件flowSequence
            Object condition = transition.getProperty(CamundaConst.CONDITION_TEXT);
            // 节点条件判定是否满足
            if (ObjectUtil.isNotNull(condition) && camundaUtil.camundaEvalExpress(condition, currentVariablesMap)) {
                // 此处为对外指向flowSequence指向满足条件的节点
                activityList.add(transition.getDestination());
            }
        }
        return activityList;
    }

    /**
     * 获取并行网关正确的下一节点信息
     * @param currentActivity 当前节点信息
     * @return
     */
    public List<PvmActivity> getNextPositionByParallelGateway(PvmActivity currentActivity) {
        // 返回结果信息
        List<PvmActivity> activityList = new ArrayList<>();

        // 获取当前并行网关节点对外连接信息 transitionList 为并行网关对外的 -> 连线
        List<PvmTransition> transitionList = currentActivity.getOutgoingTransitions();
        for (PvmTransition transition : transitionList) {
            // 获取当前任务判定条件,注意此处不是节点,为对外条件 flowSequence,获取destination则为 -> 对应后面的节点
            PvmActivity destinationActivity = transition.getDestination();
            String activityNodeType = StrUtil.toString(destinationActivity.getProperty(CamundaConst.CAMUNDA_NODE_TYPE));

            // 如果目标节点为userTask节点,则直接加入执行节点
            if (CamundaConst.USER_TASK.equals(activityNodeType)) {
                activityList.add(transition.getDestination());
            }
        }
        return activityList;
    }

}

2.5 实体类实现

2.5.1 流程获取定义信息实体
package cn.git.camunda.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * @description: 流程获取定义信息实体
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-18 05:08:05
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("ACT_RE_PROCDEF")
public class ActReProcdef implements Serializable {

    private static final long serialVersionUID=1L;

    @TableId(value = "ID_", type = IdType.ASSIGN_ID)
    private String id;

    @TableField("REV_")
    private BigDecimal rev;

    @TableField("CATEGORY_")
    private String category;

    @TableField("NAME_")
    private String name;

    @TableField("KEY_")
    private String key;

    @TableField("VERSION_")
    private BigDecimal version;

    @TableField("DEPLOYMENT_ID_")
    private String deploymentId;

    @TableField("RESOURCE_NAME_")
    private String resourceName;

    @TableField("DGRM_RESOURCE_NAME_")
    private String dgrmResourceName;

    @TableField("HAS_START_FORM_KEY_")
    private Integer hasStartFormKey;

    @TableField("SUSPENSION_STATE_")
    private BigDecimal suspensionState;

    @TableField("TENANT_ID_")
    private String tenantId;

    @TableField("VERSION_TAG_")
    private String versionTag;

    @TableField("HISTORY_TTL_")
    private BigDecimal historyTtl;

    @TableField("STARTABLE_")
    private Integer startable;
}

2.5.2 岗位信息跳转配置表
package cn.git.camunda.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * @author lixuchun
 * @title: 岗位信息跳转配置表
 * @projectName bank-credit-sy
 * @description: 岗位信息跳转配置表-entity
 * @date 2023-09-19
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("CAMUNDA_JUMP_POINT")
public class CamundaJumpPoint implements Serializable {

    private static final long serialVersionUID=1L;

    /**
     * 主键
     */
    @TableId(value = "ID", type = IdType.ASSIGN_ID)
    private String id;

    /**
     * 模板名称
     */
    @TableField("PROCESS_TEMPLATE_NAME")
    private String processTemplateName;

    /**
     * 模板key
     */
    @TableField("PROCESS_TEMPLATE_KEY")
    private String processTemplateKey;

    /**
     * 任务定义id
     */
    @TableField("ACTIVITY_ID")
    private String activityId;

    /**
     * 任务定义名称
     */
    @TableField("ACTIVITY_NAME")
    private String activityName;

    /**
     * 当前任务可跳岗位id 多个用逗号分隔
     */
    @TableField("JUMP_ACTIVITY_IDS")
    private String jumpActivityIds;

    /**
     * 流程模板版本
     */
    @TableField("TEMPLATE_VERSION")
    private String templateVersion;

    /**
     * 所属机构
     */
    @TableField("OWN_ORG")
    private String ownOrg;

    /**
     * 当前任务是否允许跳岗 0 允许 1 不允许
     */
    @TableField("NO_JUMP_FLAG")
    private String noJumpFlag;

    /**
     * 创建时间
     */
    @TableField("CREATE_TIME")
    private Date createTime;

    /**
     * 修改时间
     */
    @TableField("UPDATE_TIME")
    private Date updateTime;

    /**
     * 删除标识 0 否 1 是
     */
    @TableField("IS_DEL")
    private String isDel;
}

2.5.3 未完成任务信息
package cn.git.camunda.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
 * @description: camunda 未完成任务信息
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-13 10:06:11
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CamundaUndoTask {

    /**
     * 业务编号
     */
    public String businessKey;

    /**
     * 客户名称
     */
    public String customerName;

    /**
     * 客户编号
     */
    public String customerNum;

    /**
     * bizType 业务种类
     */
    public String bizType;

    /**
     * bizName业务种类名称
     */
    public String bizName;

    /**
     * 模板key
     */
    public String processTemplateKey;

    /**
     * 业务发起人
     */
    public String creator;

    /**
     * 发起机构号
     */
    public String orgCd;

    /**
     * 发起机构名称
     */
    public String orgName;

    /**
     * 业务发起时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
    public Date processCreateDate;

    /**
     * ActivityId
     */
    public String activityId;

    /**
     * taskId 任务id
     */
    public String taskId;

    /**
     * executionId
     */
    public String processId;

}

2.5.4 流程发起代办任务留痕表
package cn.git.camunda.entity;

import cn.git.oracle.constant.DbConstant;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * @description: 流程发起代办任务留痕表
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-12 04:08:49
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("CAMUNDA_UNFINISHED_FLOW")
public class CamundaUnfinishedFlow implements Serializable {

    private static final long serialVersionUID = -2523276919607556911L;

    /**
     * 主键id
     */
    @TableId(value = "ID", type = IdType.ASSIGN_ID)
    private String id;

    /**
     * 操作柜员cd
     */
    @TableField("OPTION_USER_CD")
    private String optionUserCd;

    /**
     * 操作机构cd
     */
    @TableField("OPTION_ORG_CD")
    private String optionOrgCd;

    /**
     * 客户号
     */
    @TableField("CUSTOMER_NUM")
    private String customerNum;

    /**
     * 客户名称
     */
    @TableField("CUSTOMER_NAME")
    private String customerName;

    /**
     * 流程processId
     */
    @TableField("PROCESS_ID")
    private String processId;

    /**
     * 业务编号
     */
    @TableField("BUSINESS_KEY")
    private String businessKey;

    /**
     * 业务名称
     */
    @TableField("BIZ_NAME")
    private String bizName;

    /**
     * 流程模板key
     */
    @TableField("PROCESS_TEMPLATE_KEY")
    private String processTemplateKey;

    /**
     * 发起系统标识
     */
    @TableField("SYSTEM_FLAG")
    private String systemFlag;

    /**
     * 创建日期
     */
    @TableField(value = "CTIME", fill = FieldFill.INSERT)
    private Date ctime;

    /**
     * 更新日期
     */
    @TableField(value = "MTIME", fill = FieldFill.UPDATE)
    private Date mtime;

    /**
     * 删除标记(0:未删除,1:已删除)
     * 逻辑删除
     */
    @TableLogic(value = DbConstant.DEFAULT_VALUE, delval = DbConstant.DELETE_VALUE)
    @TableField("IS_DEL")
    private String isDel;

}

2.6 mapper以及对应xml

2.6.1 流程获取定义信息mapper
package cn.git.camunda.mapper;

import cn.git.camunda.entity.ActReProcdef;
import cn.git.oracle.mapper.BaseMybatisPlusMapper;

/**
 * @description: 流程获取定义信息mapper
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-12 04:19:32
 */
public interface ActReProcdefMapper extends BaseMybatisPlusMapper<ActReProcdef> {
}
2.6.2 岗位信息跳转配置表 Mapper 接口
package cn.git.camunda.mapper;

import cn.git.camunda.entity.CamundaJumpPoint;
import cn.git.oracle.mapper.BaseMybatisPlusMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;

/**
 * @author lixuchun
 * @title: 岗位信息跳转配置表 Mapper 接口
 * @projectName bank-credit-sy
 * @description: 岗位信息跳转配置表-entity
 * @date 2023-09-19
 */
public interface CamundaJumpPointMapper extends BaseMybatisPlusMapper<CamundaJumpPoint> {

    /**
     * 通过id删除跳岗信息
     * @param id 主键
     */
    @Update("UPDATE CAMUNDA_JUMP_POINT SET JUMP_ACTIVITY_IDS = '' WHERE ID = #{id}")
    void deleteJumpPointsById(@Param("id") String id);

}

2.6.3 流程发起代办任务留痕表操作mapper
package cn.git.camunda.mapper;

import cn.git.camunda.entity.CamundaUnfinishedFlow;
import cn.git.oracle.mapper.BaseMybatisPlusMapper;

/**
 * @description: 流程发起代办任务留痕表操作mapper
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-12 04:19:32
 */
public interface CamundaUnfinishedFlowMapper extends BaseMybatisPlusMapper<CamundaUnfinishedFlow> {
}

2.7 工具类

camunda网关枚举信息工具类

package cn.git.camunda.util;

import camundafeel.de.odysseus.el.ExpressionFactoryImpl;
import camundafeel.de.odysseus.el.util.SimpleContext;
import camundafeel.de.odysseus.el.util.SimpleResolver;
import camundafeel.javax.el.ExpressionFactory;
import camundafeel.javax.el.ValueExpression;
import cn.git.common.exception.ServiceException;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @description: camunda流程通用类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-13 08:52:15
 */
@Slf4j
@Component
public class CamundaUtil {

    /**
     * 通过 templateKey 模板id获取enum对象
     * @param templateKey 模板id
     * @return WorkFlowProductEnum 枚举类型
     */
    public CamundaFlowTypeEnum getCamundaEnumByTemplateKey(String templateKey) {
        for (CamundaFlowTypeEnum value : CamundaFlowTypeEnum.values()) {
            if (value.getProcessTemplateKey().equals(templateKey)) {
                return value;
            }
        }
        return null;
    }

    /**
     * 表达式验证是否成立
     * @param condition 条件
     * @param variablesMap 参数map
     * @return
     */
    public Boolean camundaEvalExpress(Object condition, Map<String, Object> variablesMap) {
        try {
            boolean flag;
            String conditionStr = StrUtil.toString(condition);
            ExpressionFactory factory = new ExpressionFactoryImpl();
            SimpleContext context = new SimpleContext(new SimpleResolver());
            for (String key : variablesMap.keySet()) {
                if (conditionStr.indexOf(key) > 0) {
                    factory.createValueExpression(context, "#{" + key + "}", String.class).setValue(context, variablesMap.get(key));
                }
            }

            ValueExpression valueExpression = factory.createValueExpression(context, conditionStr, boolean.class);
            flag = (Boolean) valueExpression.getValue(context);
            return flag;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(StrUtil.format("传入表达式验证报错了condition[{}],参数信息为[{}]", condition, JSONObject.toJSONString(variablesMap)));
        }
    }

}

CamundaFlowPage分页实体

package cn.git.camunda.page;

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

import java.util.List;

/**
 * @program: bank-credit-sy
 * @description:
 * @author: lixuchun
 * @create: page流程列表分页对象类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CamundaFlowPage<T> {
    /**
     * 总记录数
     */
    private int total;

    /**
     * 结果集
     */
    private List<T> result;

    /**
     * 第几页
     */
    private int pageNum;

    /**
     * 每页记录数
     */
    private int pageSize;

    /**
     * 总页数
     */
    private int pages;
}

分页工具类

package cn.git.camunda.page;

import org.springframework.stereotype.Component;

import java.util.List;

/**
 * page分页对象
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2021-06-07
 */
@Component
public class PageUtil<T> {

    /**
     * 默认首页页码
     */
    private static final int DEFAULT_PAGE_NUM = 1;

    /**
     * 将结果进行分页显示
     * @param workMapList 要分页的流程workMapList
     * @param pageNum 页码
     * @param pageSize 每页显示多少条
     * @return list返回分页后流程结果集
     */
    public CamundaFlowPage<T> setFlowListPage(List<T> workMapList, int pageNum, int pageSize) {
        // 最终返回手工分页对象
        CamundaFlowPage<T> camundaFlowPage = new CamundaFlowPage<T>();
        // 页码,每页多少条数据属性赋值
        camundaFlowPage.setPageNum(pageNum);
        // 每页多少条
        camundaFlowPage.setPageSize(pageSize);
        // 分页总条数
        camundaFlowPage.setTotal(workMapList.size());
        // 开始位置
        int startNum = (pageNum - 1) * pageSize;
        // 总页数
        int pages = this.getTotalPage(pageSize, workMapList.size());
        camundaFlowPage.setPages(pages);
        // 页码等于最后一页
        if (pageNum > pages) {
            CamundaFlowPage page = new CamundaFlowPage<>();
            page.setPageSize(pageSize);
            page.setPageNum(DEFAULT_PAGE_NUM);
            return page;
        } else if (pageNum == pages) {
            workMapList = workMapList.subList(startNum, camundaFlowPage.getTotal());
        } else {
            int endNum = startNum + pageSize;
            workMapList = workMapList.subList(startNum, endNum);
        }
        camundaFlowPage.setResult(workMapList);
        return camundaFlowPage;
    }

    /**
     * 已经分页结果,组装分页信息
     * @param workMapList 要分页的流程workMapList
     * @param pageNum 页码
     * @param pageSize 每页显示多少条
     * @param totalCount 总共多少条
     * @return list返回分页后流程结果集
     */
    public CamundaFlowPage<T> setCountFlowListPage(List<T> workMapList, int pageNum, int pageSize, int totalCount) {
        // 最终返回手工分页对象
        CamundaFlowPage<T> camundaFlowPage = new CamundaFlowPage<T>();
        // 页码,每页多少条数据属性赋值
        camundaFlowPage.setPageNum(pageNum);
        // 每页多少条
        camundaFlowPage.setPageSize(pageSize);
        // 分页总条数
        camundaFlowPage.setTotal(totalCount);
        // 开始位置
        int startNum = (pageNum - 1) * pageSize;
        // 总页数
        int pages = this.getTotalPage(pageSize, totalCount);
        camundaFlowPage.setPages(pages);
        // 页码等于最后一页
        camundaFlowPage.setResult(workMapList);
        return camundaFlowPage;
    }

    /**
     * 根据总条数获取总页数
     * @param pageSize 每页记录数
     * @param total 总记录数
     * @return int
     */
    public int getTotalPage(int pageSize, int total){
        int pages;
        if (total % pageSize == 0) {
            pages = total / pageSize;
        } else {
            pages = total / pageSize + 1;
        }
        return pages;
    }

    /**
     * 获取开始页第几条
     * @param pageNum
     * @param pageSize
     * @return
     */
    public int getStartSizeNum(int pageNum, int pageSize) {
        if (pageNum <= 1) {
            return 0;
        }
        return (pageNum - 1) * pageSize;
    }
}

2.8 服务启动类

package cn.git.camunda;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @description: 流程服务启动类,流程引擎优化,由原有 activiti 5.22.0 -> camunda7流程引擎,有点为高扩展,高并发,取消webService调用
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-04 10:23:18
 */
@SpringBootApplication(scanBasePackages = "cn.git")
@MapperScan("cn.git.camunda.mapper")
public class CamundaApplication {
    public static void main(String[] args) {
        SpringApplication.run(CamundaApplication.class, args);
    }
}

2.9 流程初始配置文件processes.xml

注意文件目录如下
processes.xml流程初始化配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义一个进程应用的配置文件,用于配置Camunda进程应用的相关属性。 -->
<process-application
        xmlns="http://www.camunda.org/schema/1.0/ProcessApplication"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <!-- 配置进程归档属性,指定进程引擎和相关属性。 -->
    <process-archive>
        <!-- 指定使用的进程引擎,默认为default。 -->
        <process-engine>default</process-engine>
        <!-- 配置进程归档的属性。 -->
        <properties>
            <!-- 指定是否在卸载时删除进程定义,false表示不删除。 -->
            <property name="isDeleteUponUndeploy">false</property>
            <!-- 指定是否在应用部署时扫描进程定义,true表示是。 -->
            <property name="isScanForProcessDefinitions">true</property>
        </properties>
    </process-archive>

</process-application>

2.10 自定义表camunda_custom_init.sql

--自定义在途任务表
CREATE TABLE CAMUNDA_UNFINISHED_FLOW (
	id 	varchar(32) NOT NULL,
	OPTION_USER_CD varchar(20),
	OPTION_ORG_CD varchar(20),
	CUSTOMER_NUM varchar(20),
	CUSTOMER_NAME varchar(50),
	PROCESS_ID varchar(60),
	BUSINESS_KEY varchar(50),
	BIZ_NAME varchar(100),
	PROCESS_TEMPLATE_KEY varchar(50),
	SYSTEM_FLAG varchar(20),
	ctime date DEFAULT sysdate,
	mtime date DEFAULT sysdate,
	is_del varchar(1) DEFAULT 0,
	PRIMARY KEY (id)
)
COMMENT ON TABLE SCMS_RAT.CAMUNDA_UNFINISHED_FLOW IS '自定义在途任务表';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.ID IS '主键';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.OPTION_USER_CD IS '操作柜员编号';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.OPTION_ORG_CD IS '操作机构编号';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.CUSTOMER_NUM IS '客户编号';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.CUSTOMER_NAME IS '客户名称';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.PROCESS_ID IS '流程实例id';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.BUSINESS_KEY IS '业务编号';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.BIZ_NAME IS '业务名称';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.PROCESS_TEMPLATE_KEY IS '流程模板编号';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.SYSTEM_FLAG IS '发起系统标识';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.CTIME IS '创建时间';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.MTIME IS '修改时间';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_UNFINISHED_FLOW.IS_DEL IS '任务是否结束 0 否 1 是';

--岗位信息跳转配置表
CREATE TABLE CAMUNDA_JUMP_POINT (
	id varchar(32) NOT NULL,
	process_template_name varchar(100) NOT NULL,
	process_template_key varchar(100) NOT NULL,
	activity_id varchar(50) NOT NULL,
	activity_name varchar(50) NOT NULL,
	jump_activity_ids varchar(200),
	template_version varchar(10),
	own_org varchar(50),
	no_jump_flag varchar(1) DEFAULT '0',
	create_time DATE DEFAULT sysdate,
	update_time DATE DEFAULT sysdate,
	is_del varchar(1) DEFAULT '0',
	PRIMARY KEY (id)
)

COMMENT ON TABLE SCMS_RAT.CAMUNDA_JUMP_POINT IS '岗位信息跳转配置表';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.ID IS '主键';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.PROCESS_TEMPLATE_NAME IS '模板名称';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.PROCESS_TEMPLATE_KEY IS '模板key';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.ACTIVITY_ID IS '任务定义id';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.ACTIVITY_NAME IS '任务定义名称';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.JUMP_ACTIVITY_IDS IS '当前任务可跳岗位id 多个用逗号分隔';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.TEMPLATE_VERSION IS '流程模板版本';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.OWN_ORG IS '所属机构';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.NO_JUMP_FLAG IS '当前任务是否允许跳岗 0 允许 1 不允许';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.UPDATE_TIME IS '修改时间';
COMMENT ON COLUMN SCMS_RAT.CAMUNDA_JUMP_POINT.IS_DEL IS '删除标识 0 否 1 是';

2.11 log4j2.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration schema="Log4J-V2.0.xsd" monitorInterval="600">
    <Properties>
        <!-- 日志生成目录 -->
        <Property name="LOG_HOME">logs</Property>
        <!-- 日志生系统名称 -->
        <property name="FILE_NAME">camunda</property>
        <!-- 日志输出格式以及含义
            [%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}] 日期 美国时间
            [%level{length=5}] 级别
            [%X{X-B3-TraceId},%X{X-B3-SpanId}] zipkin 链路追踪信息
            [%thread-%tid] 线程id
            [%logger] // 日志输出信息
            [${sys:hostName}] hostname
            [${sys:ip}]
            [${sys:applicationName}] 应用名称
            [%F,%L,%C,%M] / [当前执行类, 行号, 全类名, 方法名称]
            [%m] 日志输出内容
            ## 自己特殊约定
            '%ex'%n 两个引号将异常包裹,打出异常时候方便解析 如何抛异常 和 换行

            实际打印日志对比
            [2021-01-12T21:00:10.615+08:00]
            [INFO]
            [main-1]
            [com.imooc.collector.Application]
            []
            []
            []
            [StartupInfoLogger.java,50,org.springframework.boot.StartupInfoLogger,logStarting]
            [Starting Application on DESKTOP-0VMS7VD with PID 5608 (D:\idea_workspace_kafka\collector\target\classes started by A in D:\idea_workspace_kafka\collector)] 日志内容
            ##
            ''
         -->
        <!-- elk日志展示 -->
        <property name="patternLayout">[%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}] [%level{length=5}] [%traceId] [%logger] [${sys:hostName}] [${sys:ip}] [${sys:applicationName}] [%F,%L,%C,%M] [%m] ## '%ex'%n</property>
    </Properties>
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="${patternLayout}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>
        <!-- 文件按照格式要求在固定目录下生成文件 "app-${FILE_NAME}.log" -->
        <RollingRandomAccessFile name="appAppender" fileName="${LOG_HOME}/app-${FILE_NAME}.log" filePattern="${LOG_HOME}/app-${FILE_NAME}-%d{yyyy-MM-dd}-%i.log" >
            <PatternLayout pattern="${patternLayout}" />
            <Policies>
                <!--
                    根据当前filePattern配置"%d{yyyy-MM-dd}",每interval天滚动一次
                    "%d{yyyy-MM-dd HH-mm}" 则为每interval分钟滚动一次
                -->
                <TimeBasedTriggeringPolicy interval="1"/>
                <!--日志文件大于500MB 滚动一次-->
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingRandomAccessFile>

        <RollingRandomAccessFile name="druidSqlRollingFile" fileName="${LOG_HOME}/druid/app-${FILE_NAME}-druid.log" filePattern="${LOG_HOME}/app-${FILE_NAME}-druid-%d{yyyy-MM-dd}-%i.log" >
            <PatternLayout pattern="${patternLayout}" />
            <Policies>
                <!--
                    根据当前filePattern配置"%d{yyyy-MM-dd}",每interval天滚动一次
                    "%d{yyyy-MM-dd HH-mm}" 则为每interval分钟滚动一次
                -->
                <TimeBasedTriggeringPolicy interval="1"/>
                <!--日志文件大于500MB 滚动一次-->
                <SizeBasedTriggeringPolicy size="500MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingRandomAccessFile>

        <!-- skywalking grpc 日志收集 -->
        <GRPCLogClientAppender name="grpc-log">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </GRPCLogClientAppender>
    </Appenders>

    <Loggers>
        <!-- 关闭kafka打印日志信息 -->
        <logger name="org.apache.kafka" level="off"/>

        <!-- 记录druid-sql 的记录-->
        <logger name="druid" level="error" additivity="false">
            <appender-ref ref="druidSqlRollingFile"/>
        </logger>

        <!-- skywalking日志 -->
        <logger name="cn.git.*" level="info" additivity="false">
            <AppenderRef ref="grpc-log"/>
        </logger>

        <!-- 业务相关 异步logger 不影响系统性能 -->
        <AsyncLogger name="cn.git.*" level="info" includeLocation="true">
            <AppenderRef ref="appAppender"/>
        </AsyncLogger>

        <root level="info">
            <AppenderRef ref="CONSOLE"/>
            <Appender-Ref ref="appAppender"/>
            <AppenderRef ref="grpc-log"/>
        </root>

    </Loggers>
</Configuration>

2.12 服务配置文件

当前为微服务环境,使用nacos作为注册中心,配置全部在nacos中,项目中配置如下:

spring:
  application:
    name: @project.artifactId@
  main:
    allow-bean-definition-overriding: true

引入discover模块,读取nacos配置文件,此配置文件中,引用符$引用通用配置文件内容,这里不进行展示了

spring:
  cloud:
    nacos:
      username: nacos
      password: xxx
      discovery:
        server-addr: xxx:8848
        namespace: UAT2_CUS
      config:
        # nacos的服务端地址
        server-addr: xxx:8848
        # 配置文件格式
        file-extension: yml
        group: GIT_GROUP
        namespace: UAT2_CUS
        # 长连接超时时间
        config-long-poll-timeout: 60000
        # 轮询重试时间
        config-retry-time: 2000
        # 长轮询最大重试次数
        max-retry: 3
        # 开启监听和自动刷新
        refresh-enabled: true
        ext-config:
          # 通用配置信息在此文件中
          - data-id: git-common-config-uat.yml
            group: GIT_GROUP
            refresh: true
            namespace: UAT2_CUS

camunda-server.yml配置文件如下

server:
  port: ${git.camunda-server.port}
spring:
  datasource:
    # camunda服务地址优化
    driver-class-name: oracle.jdbc.OracleDriver
    url: ${git.oracle.camunda-server.url}
    username: ${git.oracle.camunda-server.username}
    password: ${git.oracle.camunda-server.password}
    #初始化连接池的连接数量 大小,最小,最大
    initial-size: 5
    min-idle: 5
    max-active: 20
    #配置获取连接等待超时的时间 毫秒
    max-wait: 60000
    #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 30000
    validation-query: SELECT 1 FROM DUAL
ribbon:
  ReadTimeout: 30000
  ConnectTimeout: 30000

# SCHEMA-NAME MUST USE UPPERCASE FOR ORACLE
# Camunda BPM配置
camunda:
  # BPM(业务流程管理)模块配置
  bpm:
    # 指定默认的过程引擎名称
    process-engine-name: default
    # 控制是否自动部署流程定义文件
    # 若设置为false,则需要手动部署流程定义
    auto-deployment-enabled: false
    # 数据库相关配置
    database:
      # 设置使用的数据库类型,此处为Oracle
      type: oracle
      # 是否开启JDBC批量处理功能
      # 若设置为false,则关闭批处理,可能影响数据操作性能
      jdbc-batch-processing: false
      # 数据库模式(schema)名称
      schema-name: SCMS_RAT
      # 表名前缀,用于区分不同应用或租户的数据表
      table-prefix: SCMS_RAT.
      # 是否允许自动更新数据库模式
      # 若设置为true,将根据BPMN模型自动创建、更新数据库表结构
      schema-update: true

3 启动服务,进行测试吧

3.1 启动服务

服务启动登录页面
查看具体业务吧

3.2 测试类代码如下

专门的一个测试类controller

package cn.git.camunda.controller;

import cn.git.camunda.consts.CamundaConst;
import cn.git.camunda.dto.*;
import cn.git.camunda.service.CamundaCommonService;
import cn.git.camunda.util.CamundaFlowTypeEnum;
import cn.git.camunda.util.CamundaSysEnum;
import cn.git.camunda.util.CamundaUtil;
import com.alibaba.fastjson.JSONObject;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.variable.VariableMap;
import org.camunda.bpm.engine.variable.Variables;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description: camunda测试类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2023-09-05 11:14:00
 */
@RestController
@RequestMapping("/userTask")
public class CamundaTestController {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private IdentityService identityService;

    @Autowired
    private CamundaUtil camundaUtil;

    @Autowired
    private CamundaCommonService camundaCommonService;

    /**
     * 开始任务 任务定义key
     * @param processKey
     */
    @GetMapping("/start/{processKey}/{businessKey}")
    public void start(@PathVariable(value = "processKey") String processKey, @PathVariable(value = "businessKey") String businessKey){
        identityService.setAuthenticatedUserId("331326:3313");
        // 设置传递参数信息
        VariableMap variableMap = Variables.createVariables();
        List<String> videoNameList = new ArrayList<>();
        videoNameList.add("电影");
        videoNameList.add("电视剧");
        videoNameList.add("综艺节目");
        variableMap.putValue("videoNames", videoNameList);
        variableMap.putValue("leaveDays", 5L);
//        variableMap.putValue("processTemplateKey", "Process_exlusive_gateway_other");
        runtimeService.startProcessInstanceByKey(processKey, businessKey, variableMap);
    }

    /**
     * 开始任务 任务定义key
     * @param processKey
     */
    @GetMapping("/start/multi/{processKey}")
    public void startMulti(@PathVariable(value = "processKey") String processKey){
        identityService.setAuthenticatedUserId("javasdk");
        // 设置传递参数信息
        VariableMap variableMap = Variables.createVariables();
        // 设置多实例任务审批员
        List<String> leaderList = new ArrayList<>();
        leaderList.add("jack");
        leaderList.add("tom");
        leaderList.add("rose");
        variableMap.putValue("leaders", leaderList);
        runtimeService.startProcessInstanceByKey(processKey, variableMap);
    }

    /**
     * 开始任务 任务定义key
     * @param processKey
     */
    @GetMapping("/start/script/{processKey}")
    public void startScript(@PathVariable(value = "processKey") String processKey){
        identityService.setAuthenticatedUserId("tom");
        // 设置传递参数信息
        VariableMap variableMap = Variables.createVariables();
        variableMap.putValue("originDays", 10);
        runtimeService.startProcessInstanceByKey(processKey, variableMap);
    }

    /**
     * 事件网关,发起一个执行信号,事件网关对应方法便可以执行
     */
    @GetMapping("/signal/gateway")
    public void sendSignalGateway() {
        // 可以自定义设置参数
        runtimeService.createSignalEvent("Signal_direct_leader").send();
    }

    /**
     * 测试发起流程
     * @param processKey
     */
    @GetMapping("/start/dics/{processKey}/{time}")
    public void startDICS(@PathVariable(value = "processKey") String processKey,
                          @PathVariable(value = "time") String time) {
        CamundaStartProcessDTO camundaStartProcessDTO = new CamundaStartProcessDTO();
        camundaStartProcessDTO.setCamundaSysEnum(CamundaSysEnum.DICS);
        camundaStartProcessDTO.setOrgCd("sdk");
        camundaStartProcessDTO.setUserCd("java");
        CamundaFlowTypeEnum camundaFlowTypeEnum = camundaUtil.getCamundaEnumByTemplateKey(processKey);
        camundaStartProcessDTO.setCamundaFlowTypeEnum(camundaFlowTypeEnum);
        camundaStartProcessDTO.setBusinessKey("MCON20230913".concat(time));
        camundaStartProcessDTO.setCustomerName("大连东港橙汁制造");
        camundaStartProcessDTO.setCustomerNum("KCH202011212");
        camundaCommonService.startProcess(camundaStartProcessDTO);
    }

    /**
     * 测试结束任务
     */
    @GetMapping("/end/task")
    public void endTask() {
        CamundaEndTaskDTO endTaskDTO = new CamundaEndTaskDTO();
        endTaskDTO.setBusinessKey("MCON202309130958");
        endTaskDTO.setTaskId("7ccff2b6-5391-11ee-810b-005056c00001");
        endTaskDTO.setOrgCd("3313");
        endTaskDTO.setUserCd("331326");
        endTaskDTO.setOpinion("我去 结束了");
        endTaskDTO.setForceEndTask(CamundaConst.STR_1);
        Map<String, Object> paramMap = new HashMap<>(CamundaConst.INT_16);
        paramMap.put("approver", "331326:3313");
        endTaskDTO.setParamsMap(paramMap);
        camundaCommonService.endProcess(endTaskDTO);
    }

    /**
     * 提交方法
     */
    @GetMapping("/submit/normal")
    public void submitNormalTask() {
        CamundaSubmitTaskDTO camundaSubmitTaskDTO = new CamundaSubmitTaskDTO();
        camundaSubmitTaskDTO.setTaskId("612a9dc2-5391-11ee-810b-005056c00001");
        camundaSubmitTaskDTO.setOpinion("我同意了啊。。。。");
        camundaSubmitTaskDTO.setNextUserCd("331326");
        camundaSubmitTaskDTO.setNextOrgCd("3313");
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("leaveDays", 5L);
        camundaSubmitTaskDTO.setVariableMap(paramMap);
        CamundaSubmitTaskRspDTO camundaSubmitTaskRspDTO = camundaCommonService.submitNormalTask(camundaSubmitTaskDTO);
        System.out.println(JSONObject.toJSONString(camundaSubmitTaskRspDTO));
    }

    /**
     * 获取下一岗位
     */
    @GetMapping("/nextPosition")
    public void nextPosition() {
        CamundaNextPositionDTO camundaNextPositionDTO = new CamundaNextPositionDTO();
        camundaNextPositionDTO.setOrgCd("3313");
        camundaNextPositionDTO.setUserCd("331326");
        camundaNextPositionDTO.setTaskId("ecac9405-52c2-11ee-beeb-005056c00001");
        CamundaNextPositionRspDTO rspDTO = camundaCommonService.getNextPositionList(camundaNextPositionDTO);
        System.out.println(JSONObject.toJSONString(rspDTO));
    }

    /**
     * 流程定义信息
     * @param processDefinitionId
     */
    @GetMapping("/getProcessDefinition/{processDefinitionId}/{orgCd}")
    public void getProcessDefinition(@PathVariable(value = "processDefinitionId") String processDefinitionId,
                                     @PathVariable(value = "orgCd") String orgCd) {
        camundaCommonService.loadProcessDefinition(processDefinitionId, orgCd);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值