自学内容网 自学内容网

详解SpringCloud集成Camunda7.19实现工作流审批(四)

本篇讲述camunda的基础功能如何集成在springboot项目中,用接口去操作走通camunda流程

参考资料:

camunda数据库表结构介绍_camunda 表结构-CSDN博客

blog.csdn.net/QingXu1234/article/details/122439353?spm=1001.2014.3001.5502

snail-camunda: Camunda二次封装以及相关功能使用介绍。 中国式工作流解决方案。

JeecgBoot集成camunda实现抄送功能_camunda 抄送-CSDN博客

一、用户同步

由于camunda有自己的用户表 ACT_ID_USER,他的用户认证也需要用到自己的用户表,所以需要先将系统的用户同步到camunda的用户表里

方案

使用xxl-job定时任务,每天将系统用户表sys_user 同步到 ACT_ID_USER(过程是先在camunda数据库里建立一张用户同步中间表act_sys_user,同步的时候先根据sys_user的用户,在ACT_ID_USER表里新建或更新,之后再到act_sys_user表里新建或更新。中间表的字段集合两张表,作用是后面camunda服务查询sys_user数据的时候,只需要查询中间表,不需要再远程调用系统服务

表结构参考

  • sys_user
    在这里插入图片描述

  • act_sys_user

在这里插入图片描述

定时任务实现

@Component
@Slf4j
public class SyncUserJob {

    @Resource
    private IUserService userService;

    @XxlJob("SyncUserJobHandler")
    public void syncUsers() {
        String jobId = String.valueOf(XxlJobHelper.getJobId());
        log.info("----------用户同步定时任务开始,jobId:{}-----------",jobId);
        userService.syncUsers();
        log.info("----------用户同步定时任务执行成功,jobId:{}-----------",jobId);
        XxlJobHelper.handleSuccess("-------用户同步定时任务执行成功--------"+jobId);
    }
}

上面的jobId本来是想获取每次执行的唯一标识的,但是通过上面的方式获取到的是xxl-job任务的id,如下图

在这里插入图片描述

所以这里后续要优化成uuid之类的随机数代替

public Boolean syncUsers() {
    Long count = sysUserProvider.searchUserCount().getData();
    log.info("camunda同步用户----用户总数量:{}", count);
    if (count == null || count == 0) {
        return true;
    }
    // 设置每页的用户数量和初始化分页变量
    int pageSize = 30;
    int pageCount = (int) Math.ceil((double) count / pageSize);
    // 记录失败的用户ID
    List<Long> failedIds = new ArrayList<>();
    // 逐页同步用户
    for (int page = 1; page <= pageCount; page++) {
        log.info("camunda同步用户----当前页:{}", page);
        List<SysUsersVO> users = new ArrayList<>();
        try {
            // 获取当前页的用户
            String result = sysUserProvider.searchUsersPage(page, pageSize);
            if (StrUtil.isNotEmpty(result)) {
                // 去除转义字符
                result = StringEscapeUtils.unescapeJson(result);

                // 去掉前后的双引号
                if (result.startsWith("\"") && result.endsWith("\"")) {
                    result = result.substring(1, result.length() - 1);
                }

                users = JsonUtils.parseArray(result, SysUsersVO.class);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("获取用户列表失败,page:{},pageSize:{},e.getMessage:{}", page, pageSize,e.getMessage());
        }

        // 如果没有用户数据,跳过当前页
        if (users == null || users.isEmpty()) {
            continue;
        }

        // 进行同步操作
        for (SysUsersVO user : users) {
            try {
                // 执行用户同步操作
                syncUser(user);
                log.info("同步用户成功: {}", user.getId());
            } catch (Exception e) {
                // 记录失败的用户ID
                failedIds.add(user.getId());
                log.error("同步用户失败: {}, 原因: {}", user.getId(),e.getMessage());
            }
        }
        log.info("camunda同步用户----当前页:{}--成功", page);
    }

    if (!failedIds.isEmpty()) {
        //记录日志
        log.error("camunda同步用户,失败用户: {}", failedIds);
    }

    log.info("camunda同步用户成功");
    return true;
}
 private void syncUser(SysUsersVO user) {
        //判断是否已存在工作流系统用户
        ActSysUser actSysUser = actSysUserService.getOne(new LambdaUpdateWrapper<ActSysUser>().eq(ActSysUser::getUserId, String.valueOf(user.getId())));
        if (actSysUser == null) {
            // 新增用户
            ActSysUserDTO actSysUserDTO = new ActSysUserDTO();
            actSysUserDTO.setUserId(String.valueOf(user.getId()));
            actSysUserDTO.setCamundaUserId(user.getUserName());
            actSysUserDTO.setEmail(user.getEmail());
            actSysUserDTO.setUserName(user.getUserName());
            actSysUserDTO.setUserAvatar(user.getAvatar());
            actSysUserDTO.setPass(PASSWORD);
            actSysUserDTO.setOrganizeId(user.getOrganizeId());
            actSysUserDTO.setTenantId(user.getTenantId());
            setFirstAndLastName(user.getNickName(), user.getUserName(), actSysUserDTO);
            createUser(actSysUserDTO);
        } else {
            //更新email、firstName、lastName、organizeId、userAvatar字段
            ActSysUser actSysUser1 = new ActSysUser();

            actSysUser1.setId(actSysUser.getId());
            actSysUser1.setEmail(user.getEmail());

            ActSysUserDTO actSysUserDTO = new ActSysUserDTO();
            setFirstAndLastName(user.getNickName(), user.getUserName(), actSysUserDTO);
            actSysUser1.setFirstName(actSysUserDTO.getFirstName());
            actSysUser1.setLastName(actSysUserDTO.getLastName());

            actSysUser1.setOrganizeId(user.getOrganizeId());
            actSysUser1.setUserAvatar(user.getAvatar());
            actSysUser1.setTenantId(user.getTenantId());
            actSysUserService.updateById(actSysUser1);
        }
    }

二、用户认证

我们要自己写接口去封装camunda提供的api,而调用的时候,我们需要先告诉camunda我们是哪个用户,这时候就需要调用camunda的用户认证api了。

如下所示,将用户认证这块直接写到web拦截器里,每次请求接口就会先走用户认证的逻辑

public class HeaderInterceptorOfCamunda implements AsyncHandlerInterceptor {

    private final IdentityService identityService;

    public HeaderInterceptorOfCamunda(IdentityService identityService) {
        this.identityService = identityService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        String token = SecurityUtils.getToken();
        if (StringUtils.isNotEmpty(token)) {
            LoginUser loginUser = AuthUtil.getLoginUser(token);
            if (StringUtils.isNotNull(loginUser)) {
                AuthUtil.verifyLoginUserExpire(loginUser);
                SecurityContextHolder.set("camunda_uid" , loginUser.getUsername());
            }
        }

        identityService.setAuthenticatedUserId(SecurityContextHolder.get("camunda_uid"));

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        SecurityContextHolder.remove();
    }
}
@Configuration
@EnableWebMvc
public class WebMvcConfigOfCamunda implements WebMvcConfigurer {

    @Resource
    private IdentityService identityService;

    private static final String PATTERN = "/act/**";


    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/META-INF/resources/", "classpath:/resources/",
            "classpath:/static/", "classpath:/public/"};


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(headerInterceptorOfCamunda())
                .addPathPatterns(PATTERN);
    }

    /**
     * 自定义请求头拦截器
     */
    @Bean
    public HeaderInterceptorOfCamunda headerInterceptorOfCamunda() {
        return new HeaderInterceptorOfCamunda(identityService);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler(PATTERN)
                .addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS)
                .setCachePeriod(3600);
    }
}

注意:这种只针对于前端直接调用camunda服务接口,当我们的接口是给其他后端业务服务远程Feign调用的时候,则还需要另外在各自接口里进行用户认证逻辑

例如:

业务远程调用启动流程实例的方法里,最开头先走camunda用户认证逻辑

在这里插入图片描述

三、部署流程

我们原来使用的是自动部署方式,现在我们改用手动部署,通过自己写接口封装camunda提供的部署流程api实现。

部署流程使用的camunda api如下

  • 部署resource目录下文件

    // 从resource目录下部署
    Deployment deploy = repositoryService.createDeployment()
        .name("部署名")
        .addClasspathResource("/process/流程图文件名.bpmn")
        .deploy();
    

    这个addClasspathResource是resource目录下流程图的相对路径,如下图

在这里插入图片描述

那么这里应该传 /process/show_draw.bpmn

name则是本次部署的名称,实际用处不大,对应的是数据库表ACT_RE_DEPLOYMENTNAME_字段,如果是自动部署的就如下图所示

在这里插入图片描述

  • 部署外部上传的流程图文件

    Deployment deploy = repositoryService.createDeployment()
                                .name("部署名")
                                .addInputStream(fileName, inputStream)
                                .deploy();
    

    inputStream就是从上传的文件获取的,fileName为资源名,也就是文件名,对应数据库表ACT_GE_BYTEARRAYNAME_字段如下图

在这里插入图片描述

  • 完整代码

    public Deployment deployProcess(DeployProcessDTO deployProcessDTO) {
        try {
    
            //camunda用户认证
            String camundaUid = getCurrentCamundaUid();
            log.info("camunda_uid:{}", camundaUid);
            identityService.setAuthenticatedUserId(camundaUid);
    
            String processName = deployProcessDTO.getProcessName();
            String fileName = deployProcessDTO.getFileName();
            if (deployProcessDTO.getFile()!=null) {
                //上传文件部署
                String fileUrl = deployProcessDTO.getFile().getFileUrl();
                //下载文件并将其保存到临时文件中
                URL url = new URL(fileUrl);
                File tempFile = File.createTempFile("deploy", ".bpmn");
                Path path = tempFile.toPath();
                try (InputStream in = url.openStream()) {
                    Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
                }
    
                try {
                    @Cleanup
                    InputStream inputStream = Files.newInputStream(path);
                    Deployment deploy = repositoryService.createDeployment()
                            .name(StrUtil.isNotEmpty(processName) ? processName : "流程-" + fileName)
                            .addInputStream(fileName, inputStream)
                            .deploy();
    
                    // 保存到部署记录表
    
                    log.info("部署流程成功,部署ID:{}, bpmn:{}, processName:{}, fileUrl: {}", deploy.getId(), fileName, processName, fileUrl);
                    return deploy;
                } finally {
                    // 清理临时文件
                    if (!tempFile.delete()) {
                        log.warn("无法删除临时文件: {}", tempFile.getPath());
                    }
                }
            } else {
                // 从resource目录下部署
                Deployment deploy = repositoryService.createDeployment()
                        .name(StrUtil.isNotEmpty(processName) ? processName : "流程-" + fileName)
                        .addClasspathResource(BpmnConstants.BPMN_PATH_PREFIX + fileName)
                        .deploy();
                log.info("部署流程成功,部署ID:{}, bpmn:{}, processName:{}", deploy.getId(), fileName, processName);
                return deploy;
            }
    
        } catch (Exception e) {
            log.error("部署流程失败", e);
            throw new ServiceException("部署流程失败");
        }
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class DeployProcessDTO {
    
        @ApiModelProperty(value = "文件名")
        @NotEmpty(message = "文件名不能为空")
        private String fileName;
    
        @ApiModelProperty(value = "流程名")
        @NotEmpty(message = "流程名不能为空")
        private String processName;
    
        @ApiModelProperty(value = "流程图文件(bpmn)")
        private FileDTO file;
    }
    

四、查看可启动流程列表

部署好流程图后,要让用户知道有哪些流程,就需要一个查询所有可启动流程的接口。

对应的camunda api【查询流程定义(分页)】:

// 默认查询所有激活的流程定义
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery().active().latestVersion();
List<ProcessDefinition> totalList = query.list();
// 所有流程的总数
long totalCount = totalList.size();
//分页查询
List<ProcessDefinition> list = query.listPage(startIndex, pageSize);

完整代码

当是查询流程类型时,则不需要筛选active为1

    /**
     * 分页查询所有流程定义信息
     */
    @ApiOperation("分页查询所有流程定义信息")
    @GetMapping("/get-active-process-list")
    public ResponseResult<PageResult<ProcessDefinitionInfoVO>> getActiveProcessList(
            @ApiParam(value = "页数") @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
            @ApiParam(value = "每页数量") @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
            @ApiParam(value = "流程定义key") @RequestParam(value = "key",required = false) String key,
            @ApiParam(value = "流程定义名称") @RequestParam(value = "name",required = false) String name,
            @ApiParam(value = "是否为查询流程类型") @RequestParam(value = "getType",required = false) Boolean getType) {
        return ResponseResult.success(processService.getActiveProcessList(pageNum, pageSize, key, name, getType));
    }
public PageResult<ProcessDefinitionInfoVO> getActiveProcessList(Integer pageNum, Integer pageSize, String key, String name, Boolean getType) {
        // 计算起始索引
        int startIndex = (pageNum - 1) * pageSize;

        // 默认查询所有激活的流程定义,只有当getType==true时才查询所有流程定义
        ProcessDefinitionQuery query = (getType != null && getType) ?
                repositoryService.createProcessDefinitionQuery().latestVersion() : repositoryService.createProcessDefinitionQuery().active().latestVersion();
        // 补充条件搜索
        if (StrUtil.isNotEmpty(key)) {
            query.processDefinitionKeyLike("%" + key + "%");
        }
        if (StrUtil.isNotEmpty(name)) {
            query.processDefinitionNameLike("%" + name + "%");
        }

        List<ProcessDefinition> totalList = query.list();

        // 所有流程的总数
        long totalCount = totalList.size();

        List<ProcessDefinition> list = query.listPage(startIndex, pageSize);

        List<ProcessDefinitionInfoVO> resultList = list.stream().map(processDefinition -> {
            ProcessDefinitionInfoVO info = new ProcessDefinitionInfoVO();
            info.setId(processDefinition.getId());
            info.setKey(processDefinition.getKey());
            info.setName(processDefinition.getName());
            info.setVersion(processDefinition.getVersion());
            info.setActive(!processDefinition.isSuspended());
            return info;
        }).collect(Collectors.toList());

        // 返回分页结果
        IPage<ProcessDefinitionInfoVO> iPage = new Page<>(pageNum, pageSize);
        iPage.setTotal(totalCount);
        iPage.setRecords(resultList);
        return new PageResult<>(iPage);

    }

五、启动流程

对应的camunda api如下

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);

processDefinitionKey :流程定义key,即画流程图的时候的ID,如下图所示

在这里插入图片描述

businessKey:业务id,上面有讲过

variables:流程变量map,这里的map是 Map<String, Object>,如下图所示,即key是参数名,value是Object类型,可以支持的类型如Integer、Long、String等

完整代码

public Boolean startProcess(StartProcessDTO startProcessDTO) {
    //camunda用户认证
    String camundaUid = getCurrentCamundaUid();
    log.info("camunda_uid:{}", camundaUid);
    identityService.setAuthenticatedUserId(camundaUid);

    //判断该businessKey是否已存在
    String processDefinitionId = processInstanceMapper.getLatestProcessDefinitionIdByKey(startProcessDTO.getProcessDefinitionKey());
    BaseAssert.isTrue(processInstanceMapper.getRunningBusinessKeys(processDefinitionId).contains(startProcessDTO.getBusinessKey()), "该businessKey存在进行中的流程");

    Map<String, Object> variables = new HashMap<>(16);
    //设置流程变量
    if (CollectionUtil.isNotEmpty(startProcessDTO.getVariables())) {
        startProcessDTO.getVariables().forEach((name, variableDTO) -> {
            Object value = ConvertUtils.convertValue(variableDTO.getValue(), variableDTO.getType());
            variables.put(name, value);
        });
    }

    try {
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(startProcessDTO.getProcessDefinitionKey(), startProcessDTO.getBusinessKey(), variables);
        HistoricProcessInstance historicProcessInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(processInstance.getId())
                .singleResult();
        String starter = "";
        // 获取流程启动人
        if (historicProcessInstance != null) {
            starter = historicProcessInstance.getStartUserId();
        }
        log.info("启动流程成功,流程ID:{},流程启动人:{}", processInstance.getId(), starter);
        return true;
    } catch (Exception e) {
        log.error("启动流程失败", e);
        throw new ServiceException("启动流程失败");
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StartProcessDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "流程定义key")
    @NotEmpty(message = "流程定义key不能为空")
    private String processDefinitionKey;

    @ApiModelProperty(value = "businessKey")
    @NotEmpty(message = "businessKey不能为空")
    private String businessKey;

    @ApiModelProperty(value = "流程变量")
    private Map<String, VariableDTO> variables;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VariableDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "变量值")
    private Object value;
    @ApiModelProperty(value = "变量类型,首字母大写,如Long")
    private String type;
}

六、查询待办任务

相关的camunda api如下

TaskQuery query = taskService.createTaskQuery()
    .taskAssignee(assignee)
    .orderByTaskCreateTime()
    .desc();
taskList = (List) query.listPage(startIndex, pageSize);

完整代码

    /**
     * 分页查看当前用户的待办任务
     *
     */
    @ApiOperation("分页查看当前用户的待办任务")
    @GetMapping("/get-tasks-by-assignee")
    public ResponseResult<PageResult<TaskEntityVO>> getTasksByAssignee(@ApiParam(value = "页数") @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
                                                                       @ApiParam(value = "每页数量") @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
                                                                       @ApiParam(value = "流程类型名称") @RequestParam(value = "processType", required = false) String processType) {
        return ResponseResult.success(taskService.getTasksByAssignee(pageNum, pageSize, processType));
    }
    public PageResult<TaskEntityVO> getTasksByAssignee(Integer pageNum, Integer pageSize, String processType) {
       

        // 获取当前登录用户
        String assignee = getCurrentCamundaUid();

        // 计算起始索引
        int startIndex = (pageNum - 1) * pageSize;

        List<TaskEntity> taskList = new ArrayList<>();
        TaskQuery query = StrUtil.isNotEmpty(processType) ?
                taskService.createTaskQuery()
                        .taskAssignee(assignee)
                        .processDefinitionNameLike("%" + processType + "%")
                        .orderByTaskCreateTime()
                        .desc() :
                taskService.createTaskQuery()
                        .taskAssignee(assignee)
                        .orderByTaskCreateTime()
                        .desc();
        taskList = (List) query.listPage(startIndex, pageSize);
        // 所有流程的总数,用于分页
        long totalCount = query.count();
        IPage<TaskEntityVO> iPage = new Page<>(pageNum, pageSize);
        List<TaskEntityVO> list = new ArrayList<>();
        
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    // 核心线程数
                    5,
                    // 最大线程数
                    10,
                    // 空闲线程的存活时间
                    1,
                    // 存活时间单位
                    TimeUnit.MINUTES,
                    // 任务队列,队列大小为100
                    new ArrayBlockingQueue<>(100)
            );

            List<Future<TaskEntityVO>> futureList = taskList.stream()
                    .map(task -> executor.submit(() -> {
                        //根据taskId获取流程实例
                        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                                .processInstanceId(task.getProcessInstanceId())
                                .singleResult();
                        ActSysUser startActSysUser = getOneActSysUser(historicProcessInstance.getStartUserId());
                        ActSysUser actSysUser = getOneActSysUser(task.getAssignee());
                        String businessKey = getBusinessKeyByTaskId(task.getId());
                        return TaskEntityVO.builder()
                                .id(task.getId())
                                .name(replaceTaskNameAssignee(task.getProcessInstanceId(), task.getAssignee(), task.getId()))
                                .nodeName(task.getName())
                                .assignee(task.getAssignee())
                                .userId(actSysUser != null ? Long.valueOf(actSysUser.getUserId()) : null)
                                .userNickName(actSysUser != null ? actSysUser.getLastName() + actSysUser.getFirstName() : "")
                                .startAssignee(historicProcessInstance.getStartUserId())
                                .startUserId(startActSysUser != null ? Long.valueOf(startActSysUser.getUserId()) : null)
                                .startUserNickName(startActSysUser != null ? startActSysUser.getLastName() + startActSysUser.getFirstName() : "")
                                .processDefinitionKey(processService.getProcessDefinitionKey(task.getProcessDefinitionId()))
                                .processType(processService.getProcessType(processService.getProcessDefinitionKey(task.getProcessDefinitionId())))
                                .processInstanceId(task.getProcessInstanceId())
                                .executionId(task.getExecutionId())
                                .businessKey(businessKey)
                                .variables(getVariablesByTaskId(task.getId()))
                                .createTime(LocalDateTime.ofInstant(task.getCreateTime().toInstant(), ZoneId.systemDefault()))
                                .processCompleted(processInstanceMapper.completed(task.getProcessInstanceId()))
                                .build();
                    })).collect(Collectors.toList());

            for (Future<TaskEntityVO> future : futureList) {
                try {
                    list.add(future.get());
                } catch (InterruptedException | ExecutionException e) {
                    log.error("Error while getting task results", e);
                    e.printStackTrace();
                }
            }

            // 关闭线程池
            executor.shutdown();
       
        iPage.setTotal(totalCount);
        iPage.setRecords(list);
        return new PageResult<>(iPage);
    }

获取流程类型,这里实际用的是流程定义名,即对应表ACT_RE_PROCDEFNAME_字段

public String getProcessType(String processDefinitionKey) {
    // 查询最新的流程定义
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
            .processDefinitionKey(processDefinitionKey)
            .latestVersion()
            .singleResult();
    if (processDefinition != null) {
        return processDefinition.getName();
    }
    return "";
}

获取流程变量,这里注意有当前表ACT_RU_VARIABLE 和 历史表 ACT_HI_VARINST两张表

public String getVariableValue(String processInstanceId, String variableName) {
    // 获取默认的 ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    // 获取 RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();

    // 获取变量值
    Object variableValue = null;
    try {
        variableValue = runtimeService.getVariable(processInstanceId, variableName);
    } catch (NullValueException e) {
        log.info("历史流程变量,processInstanceId:{},variableName:{}",processInstanceId,variableName);
        // 如果运行时不存在该变量,则尝试从历史记录中获取
        List<HistoricVariableInstance> historicVariableInstances = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(processInstanceId)
                .variableName(variableName)
                .list();

        // 如果历史变量实例存在,则获取最新的一个
        if (!historicVariableInstances.isEmpty()) {
            // 按照创建时间排序,取最后一个(最新的)
            HistoricVariableInstance latestVariable = historicVariableInstances.stream()
                    .max(Comparator.comparing(HistoricVariableInstance::getCreateTime))
                    .orElse(null);

            if (latestVariable.getValue() != null) {
                log.info("历史变量值: {}", latestVariable.getValue());
                return latestVariable.getValue().toString();
            }
        }
    }

    if (variableValue == null) {
        return "";
    }
    // 类型化的变量值
    TypedValue typedVariableValue = runtimeService.getVariableTyped(processInstanceId, variableName);

    if (typedVariableValue == null ) {
        return "";
    }

    log.info("变量值: {}", variableValue);
    log.info("类型化的变量值: {}", typedVariableValue.getValue());

    if (typedVariableValue.getValue() == null) {
        return "";
    }

    return typedVariableValue.getValue().toString();
}

同上

private Map<String, Object> getVariablesByTaskId(String taskId) {
    // 尝试获取当前任务
    Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();

    if (currentTask != null) {
        // 如果是当前任务,获取执行ID并返回流程变量
        String executionId = currentTask.getExecutionId();
        return runtimeService.getVariables(executionId);
    } else {
        // 如果不是当前任务,查询历史任务
        HistoricTaskInstance historicTask = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();

        if (historicTask != null) {
            String processInstanceId = historicTask.getProcessInstanceId();
            // 获取历史流程实例的变量
            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .list();
            return variables.stream()
                    .collect(Collectors.toMap(
                            HistoricVariableInstance::getName,
                            HistoricVariableInstance::getValue,
                            (existingValue, newValue) -> {
                                // 如果有重复的键,比较创建时间,保留最新的
                                HistoricVariableInstance existingVar = variables.stream()
                                        .filter(var -> var.getValue().equals(existingValue))
                                        .findFirst()
                                        .orElse(null);

                                HistoricVariableInstance newVar = variables.stream()
                                        .filter(var -> var.getValue().equals(newValue))
                                        .findFirst()
                                        .orElse(null);

                                if (existingVar != null && newVar != null) {
                                    return existingVar.getCreateTime().after(newVar.getCreateTime()) ? existingVar.getValue() : newVar.getValue();
                                }
                                return existingValue;
                            }
                    ));
        }
    }

    return Collections.emptyMap();
}
/**
 * 判断流程是否已经结束
 * @param processInstanceId
 * @return
 */
@Select("SELECT COUNT(1) FROM ACT_HI_PROCINST WHERE ID_ = #{processInstanceId} AND STATE_ = 'COMPLETED'")
Boolean completed(@Param("processInstanceId") String processInstanceId);

这里是为了实现从任务列表可以跳转到对应业务的详情页

businessKey在本项目中的定义规则是:流程定义key-数据库-流程表名-业务数据id-rabbitmq路由键【后续会用到rabbitmq在流程结束时发送消息给业务服务来实现对业务服务数据库操作】

/**
 * 获取某任务的流程表路由
 * @param businessKey
 * @return
 */
private String getFlowTableRouteByBusinessKey(String businessKey) {
    try {
        //截取businessKey第二个"-"和第三个"-"之间的字符串
        int firstDashIndex = businessKey.indexOf('-');
        int secondDashIndex = businessKey.indexOf('-', firstDashIndex + 1);
        int thirdDashIndex = businessKey.indexOf('-', secondDashIndex + 1);
        String flowTableName = businessKey.substring(secondDashIndex + 1, thirdDashIndex);
        ActFlowTableInfo actFlowTableInfo = actFlowTableInfoService
                .getOne(new LambdaQueryWrapper<ActFlowTableInfo>().eq(ActFlowTableInfo::getFlowTableName, flowTableName).last("LIMIT 1"));
        return actFlowTableInfo.getFlowTableRoute();
    } catch (Exception e) {
        log.error("获取流程表路由失败,businessKey: {},msg:{}", businessKey, e.getMessage());
        return "";
    }
}

七、查询任务详情

任务详情是上面任务列表的补充接口,将部分不需要在列表页展示的信息拆到这个接口里,以优化列表接口的响应速度

完整代码

public TaskEntityVO getTaskEntityDetailByTaskId(String taskId) {
    // 判断是否是历史任务
    Task task1 = getTaskByIdWithoutAssert(taskId);
    // 未完成任务
    if (task1 != null) {
        TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult();
        BaseAssert.isNull(task, "任务不存在");
        //根据taskId获取流程实例
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
        ActSysUser startActSysUser = actSysUserService.getOne(new LambdaQueryWrapper<ActSysUser>().eq(ActSysUser::getCamundaUserId, historicProcessInstance.getStartUserId()));
        ActSysUser actSysUser = actSysUserService.getOne(new LambdaQueryWrapper<ActSysUser>().eq(ActSysUser::getCamundaUserId, task.getAssignee()));
        String businessKey = getBusinessKeyByTaskId(task.getId());
        return TaskEntityVO.builder()
                .id(task.getId())
                .name(replaceTaskNameAssignee(task.getProcessInstanceId(), task.getAssignee(), task.getId()))
                .nodeName(task.getName())
                .assignee(task.getAssignee())
                .userId(actSysUser != null ? Long.valueOf(actSysUser.getUserId()) : null)
                .userNickName(actSysUser != null ? actSysUser.getLastName() + actSysUser.getFirstName() : "")
                .startAssignee(historicProcessInstance.getStartUserId())
                .startUserId(startActSysUser != null ? Long.valueOf(startActSysUser.getUserId()) : null)
                .startUserNickName(startActSysUser != null ? startActSysUser.getLastName() + startActSysUser.getFirstName() : "")
                .processDefinitionKey(processService.getProcessDefinitionKey(task.getProcessDefinitionId()))
                .processType(processService.getProcessType(processService.getProcessDefinitionKey(task.getProcessDefinitionId())))
                .processInstanceId(task.getProcessInstanceId())
                .executionId(task.getExecutionId())
                .businessKey(businessKey)
                .flowTableRoute(getFlowTableRouteByBusinessKey(businessKey))
                .dataId(processService.getVariableValue(task.getProcessInstanceId(), "dataId"))
                .editFlag(processService.getVariableValue(task.getProcessInstanceId(), "editFlag"))
                .variables(getVariablesByTaskId(task.getId()))
                .createTime(LocalDateTime.ofInstant(task.getCreateTime().toInstant(), ZoneId.systemDefault()))
                .commentVOList(commentService.getCommentsByProcessInstanceId(task.getProcessInstanceId()))
                .currentNode(getCurrentNodeByTaskId(task.getId()))
                .historyNodeList(getHistoryNodeListByTaskId(task.getId()))
                .processCompleted(processInstanceMapper.completed(task.getProcessInstanceId()))
                .build();
    } else {
        HistoricTaskInstance historicTask = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
        BaseAssert.isNull(historicTask, "任务不存在");
        //根据taskId获取流程实例
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(historicTask.getProcessInstanceId())
                .singleResult();
        ActSysUser startActSysUser = actSysUserService.getOne(new LambdaQueryWrapper<ActSysUser>().eq(ActSysUser::getCamundaUserId, historicProcessInstance.getStartUserId()));
        ActSysUser actSysUser = actSysUserService.getOne(new LambdaQueryWrapper<ActSysUser>().eq(ActSysUser::getCamundaUserId, historicTask.getAssignee()));
        String businessKey = getBusinessKeyByTaskId(historicTask.getId());
        return TaskEntityVO.builder()
                .id(historicTask.getId())
                .name(replaceTaskNameAssignee(historicTask.getProcessInstanceId(), historicTask.getAssignee(), historicTask.getId()))
                .nodeName(historicTask.getName())
                .assignee(historicTask.getAssignee())
                .userId(actSysUser != null ? Long.valueOf(actSysUser.getUserId()) : null)
                .userNickName(actSysUser != null ? actSysUser.getLastName() + actSysUser.getFirstName() : "")
                .startAssignee(historicProcessInstance.getStartUserId())
                .startUserId(startActSysUser != null ? Long.valueOf(startActSysUser.getUserId()) : null)
                .startUserNickName(startActSysUser != null ? startActSysUser.getLastName() + startActSysUser.getFirstName() : "")
                .processDefinitionKey(processService.getProcessDefinitionKey(historicTask.getProcessDefinitionId()))
                .processType(processService.getProcessType(processService.getProcessDefinitionKey(historicTask.getProcessDefinitionId())))
                .processInstanceId(historicTask.getProcessInstanceId())
                .executionId(historicTask.getExecutionId())
                .businessKey(businessKey)
                .flowTableRoute(getFlowTableRouteByBusinessKey(businessKey))
                .dataId(processService.getVariableValue(historicTask.getProcessInstanceId(), "dataId"))
                .editFlag("0")
                .variables(getVariablesByTaskId(historicTask.getId()))
                .createTime(LocalDateTime.ofInstant(historicTask.getStartTime().toInstant(), ZoneId.systemDefault()))
                .endTime(historicTask.getEndTime() == null ? null : LocalDateTime.ofInstant(historicTask.getEndTime().toInstant(), ZoneId.systemDefault()))
                .commentVOList(commentService.getCommentsByProcessInstanceId(historicTask.getProcessInstanceId()))
                .currentNode(getCompleteTaskCurrentNodeByTaskId(historicTask.getId()))
                .historyNodeList(getHistoryNodeListByTaskId(historicTask.getId()))
                .processCompleted(processInstanceMapper.completed(historicTask.getProcessInstanceId()))
                .build();
    }
}

补充点1:评论列表

public List<CommentVO> getCommentsByProcessInstanceId(String processInstanceId) {
    List<CommentVO> comments = new ArrayList<>();

    // 查询当前未完成的任务
    List<Task> activeTasks = taskService.createTaskQuery()
            .processInstanceId(processInstanceId)
            .list();

    // 查询已完成的任务
    List<HistoricTaskInstance> completedTasks = historyService.createHistoricTaskInstanceQuery()
            .processInstanceId(processInstanceId)
            .finished()
            .list();

    // 获取当前未完成任务的评论
    for (Task task : activeTasks) {
        List<Comment> taskComments = taskService.getTaskComments(task.getId());
        String taskName = task.getName();
        for (Comment comment : taskComments) {
            comments.add(convertToCommentVO(comment, taskName));
        }
    }

    // 获取已完成任务的评论
    for (HistoricTaskInstance historicTask : completedTasks) {
        List<Comment> historicComments = taskService.getTaskComments(historicTask.getId());
        String taskName = historicTask.getName();
        for (Comment comment : historicComments) {
            comments.add(convertToCommentVO(comment, taskName));
        }
    }

    // 按时间倒序排列
    comments.sort(Comparator.comparing(CommentVO::getTime).reversed());

    return comments;
}

补充点2:当前节点信息

public NodeVO getCurrentNodeByTaskId(String taskId) {
        //根据任务id查询任务
        Task task = getTaskById(taskId);
        // 获取任务的流程定义 ID
        String processDefinitionId = task.getProcessDefinitionId();
        // 获取任务的流程定义 KEY(截取processDefinitionId第一个冒号前面的字符串)
        String processDefinitionKey = StrUtil.isNotEmpty(processDefinitionId) ? processDefinitionId.substring(0, processDefinitionId.indexOf(":")) : "";
        // 当前节点 ID
        String taskDefinitionKey = task.getTaskDefinitionKey();
        // 获取当前节点 NodeVO
        NodeVO currentNodeVO = actInstanceMapper.getCurrentActNodeByProcessInstanceIdAndActId(task.getProcessInstanceId(), taskDefinitionKey);
        // 获取 BPMN 模型
        BpmnModelInstance modelInstance = repositoryService.getBpmnModelInstance(processDefinitionId);
        // 获取节点
        FlowNode flowNode = modelInstance.getModelElementById(taskDefinitionKey);
        // 获取节点名称
        String nodeName = flowNode.getName();
        // 获取手动指定审批人类型
        String manualAssignType = getManualAssignType(processDefinitionKey, taskDefinitionKey);
        // 获取流程审批节点审批人变量信息
        ActApproveNodeAssigneeVariableVO vo = new ActApproveNodeAssigneeVariableVO();
        vo = !"0".equals(manualAssignType) ? getActApproveNodeAssigneeVariableVO(processDefinitionKey, taskDefinitionKey) : null;
        return NodeVO.builder()
                .id(taskDefinitionKey)
                .name(nodeName)
                .returnType(getReturnTypeByProcDefKey(processDefinitionKey, taskDefinitionKey))
                .ccType(getCcTypeByProcDefKey(processDefinitionKey, taskDefinitionKey))
                .actCcVariableVOList(getActCcVariableVOList(processDefinitionKey, taskDefinitionKey, task.getProcessInstanceId()))
                .manualAssignType(manualAssignType)
                .actApproveNodeAssigneeVariableVO(vo)
                .assigneeVOList(processService.getAssigneeVOList(task.getProcessInstanceId(), taskDefinitionKey, true))
                .startTime(currentNodeVO.getStartTime())
                .endTime(currentNodeVO.getEndTime())
                .build();
    }

补充点3:历史节点列表

public List<NodeVO> getHistoryNodeListByTaskId(String taskId) {
        List<NodeVO> nodeVOS = new ArrayList<>();
        //根据任务id当前任务所在流程的最新的任务
        HistoricTaskInstance historicTask = getHistoricTaskByIdWithoutAssert(taskId);
        if (historicTask == null) {
            return nodeVOS;
        }
        // 获取任务的流程定义 ID
        String processDefinitionId = historicTask.getProcessDefinitionId();
        // 获取任务的流程定义 KEY(截取processDefinitionId第一个冒号前面的字符串)
        String processDefinitionKey = StrUtil.isNotEmpty(processDefinitionId) ? processDefinitionId.substring(0, processDefinitionId.indexOf(":")) : "";
        // 获取流程实例id
        String processInstanceId = historicTask.getProcessInstanceId();
        // 根据流程实例id查询所有历史节点
        List<NodeVO> actNodes = actInstanceMapper.getActNodeListByProcessInstanceId(processInstanceId);
        // 获取 BPMN 模型
        BpmnModelInstance modelInstance = repositoryService.getBpmnModelInstance(processDefinitionId);
        for (NodeVO actNode : actNodes) {
            String actId = actNode.getId();
            // 获取节点
            FlowNode flowNode = modelInstance.getModelElementById(actId);
            // 忽略非流程图上画的节点
            if (flowNode == null) {
                continue;
            }
            if (flowNode instanceof Gateway) {
                continue;
            }
            // 获取节点名称
            String nodeName = flowNode.getName();
            // 获取手动指定审批人类型
            String manualAssignType = getManualAssignType(processDefinitionKey, actId);
            // 获取流程审批节点审批人变量信息
            ActApproveNodeAssigneeVariableVO vo = new ActApproveNodeAssigneeVariableVO();
            vo = !"0".equals(manualAssignType) ? getActApproveNodeAssigneeVariableVO(processDefinitionKey, actId) : null;
            nodeVOS.add(NodeVO.builder()
                    .id(actId)
                    .name(nodeName)
                    .startTime(actNode.getStartTime())
                    .endTime(actNode.getEndTime())
                    .build());
        }
        return nodeVOS;
    }

八、查询已办任务

相关的camunda api

逻辑和查询待办基本一致,区别是待办查询的是当前表,而已办查询的是历史表

HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
                        .taskAssignee(assignee)
                        .finished()
                        .orderByHistoricTaskInstanceEndTime()
                        .desc();
historicTasks = (List) query.listPage(startIndex, pageSize);

完整代码

public PageResult<TaskEntityVO> getCompetedTasksByAssignee(Integer pageNum, Integer pageSize, String processType) {

        // 获取当前登录用户
        String assignee = getCurrentCamundaUid();

        // 计算起始索引
        int startIndex = (pageNum - 1) * pageSize;

        List<HistoricTaskInstance> historicTasks = new ArrayList<>();
        HistoricTaskInstanceQuery query = StrUtil.isNotEmpty(processType) ?
                historyService.createHistoricTaskInstanceQuery()
                        .taskAssignee(assignee)
                        .processDefinitionName(processType)
                        .finished()
                        .orderByHistoricTaskInstanceEndTime()
                        .desc() :
                historyService.createHistoricTaskInstanceQuery()
                        .taskAssignee(assignee)
                        .finished()
                        .orderByHistoricTaskInstanceEndTime()
                        .desc();
        historicTasks = (List) query.listPage(startIndex, pageSize);
        // 所有流程的总数,用于分页
        long totalCount = query.count();
        IPage<TaskEntityVO> iPage = new Page<>(pageNum, pageSize);
        List<TaskEntityVO> list = new ArrayList<>();
        if (historicTasks != null && !historicTasks.isEmpty()) {

            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    // 核心线程数
                    5,
                    // 最大线程数
                    10,
                    // 空闲线程的存活时间
                    1,
                    // 存活时间单位
                    TimeUnit.MINUTES,
                    // 任务队列,队列大小为100
                    new ArrayBlockingQueue<>(100)
            );
            List<Future<TaskEntityVO>> futureList = historicTasks.stream()
                    .map(historicTask -> executor.submit(() -> {
                        //根据taskId获取流程实例
                        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                                .processInstanceId(historicTask.getProcessInstanceId())
                                .singleResult();
                        ActSysUser startActSysUser = getOneActSysUser(historicProcessInstance.getStartUserId());
                        ActSysUser actSysUser = getOneActSysUser(historicTask.getAssignee());
                        String businessKey = getBusinessKeyByTaskId(historicTask.getId());
                        return TaskEntityVO.builder()
                                .id(historicTask.getId())
                                .name(replaceTaskNameAssignee(historicTask.getProcessInstanceId(), historicTask.getAssignee(), historicTask.getId()))
                                .nodeName(historicTask.getName())
                                .assignee(historicTask.getAssignee())
                                .userId(actSysUser != null ? Long.valueOf(actSysUser.getUserId()) : null)
                                .userNickName(actSysUser != null ? actSysUser.getLastName() + actSysUser.getFirstName() : "")
                                .startAssignee(historicProcessInstance.getStartUserId())
                                .startUserId(startActSysUser != null ? Long.valueOf(startActSysUser.getUserId()) : null)
                                .startUserNickName(startActSysUser != null ? startActSysUser.getLastName() + startActSysUser.getFirstName() : "")
                                .processDefinitionKey(processService.getProcessDefinitionKey(historicTask.getProcessDefinitionId()))
                                .processType(processService.getProcessType(processService.getProcessDefinitionKey(historicTask.getProcessDefinitionId())))
                                .processInstanceId(historicTask.getProcessInstanceId())
                                .executionId(historicTask.getExecutionId())
                                .businessKey(businessKey)
                                .flowTableRoute(getFlowTableRouteByBusinessKey(businessKey))
                                .dataId(processService.getVariableValue(historicTask.getProcessInstanceId(), "dataId"))
                                .editFlag("0")
                                .createTime(LocalDateTime.ofInstant(historicTask.getStartTime().toInstant(), ZoneId.systemDefault()))
                                .endTime(historicTask.getEndTime() == null ? null : LocalDateTime.ofInstant(historicTask.getEndTime().toInstant(), ZoneId.systemDefault()))
                                .processCompleted(processInstanceMapper.completed(historicTask.getProcessInstanceId()))
                                .build();
                    })).collect(Collectors.toList());
            for (Future<TaskEntityVO> future : futureList) {
                try {
                    list.add(future.get());
                } catch (InterruptedException | ExecutionException e) {
                    log.error("Error while getting task results", e);
                    e.printStackTrace();
                }
            }

            // 关闭线程池
            executor.shutdown();

        }
        iPage.setTotal(totalCount);
        iPage.setRecords(list);
        return new PageResult<>(iPage);
    }

九、查询我发起的流程

“我发起的”和待办、已办有所不同,因为“我发起的”流程里不一定有“我”的任务(只是流程发起者,而在后面的用户任务节点里没有“我”),所以“我发起的”列表实则是查询的流程实例列表。

相关的camunda api:

// 获取用户发起的所有流程实例
List<HistoricProcessInstance> processInstances = new ArrayList<>();
HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
    .startedBy(assignee)
    .orderByProcessInstanceStartTime()
    .desc();
processInstances = query.listPage(startIndex, pageSize);

完整代码

public PageResult<ProcessDetailVO> getProcessListStartByMe(Integer pageNum, Integer pageSize, String processType) {
        // 获取当前登录用户
        String assignee = getCurrentCamundaUid();

        // 计算起始索引
        int startIndex = (pageNum - 1) * pageSize;

        // 获取用户发起的所有流程实例
        List<HistoricProcessInstance> processInstances = new ArrayList<>();
        HistoricProcessInstanceQuery query = StrUtil.isNotEmpty(processType) ?
                historyService.createHistoricProcessInstanceQuery()
                        .startedBy(assignee)
                        .processDefinitionNameLike("%" + processType + "%")
                        .orderByProcessInstanceStartTime()
                        .desc() :
                historyService.createHistoricProcessInstanceQuery()
                        .startedBy(assignee)
                        .orderByProcessInstanceStartTime()
                        .desc();

        processInstances = query.listPage(startIndex, pageSize);

        // 所有流程的总数,用于分页
        long totalCount = query.count();
        IPage<ProcessDetailVO> iPage = new Page<>(pageNum, pageSize);
        List<ProcessDetailVO> voList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(processInstances)) {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    // 核心线程数
                    5,
                    // 最大线程数
                    10,
                    // 空闲线程的存活时间
                    1,
                    // 存活时间单位
                    TimeUnit.MINUTES,
                    // 任务队列,队列大小为100
                    new ArrayBlockingQueue<>(100)
            );
            List<Future<ProcessDetailVO>> futureList = processInstances.stream()
                    .map(item -> executor.submit(() -> {
                        String currentNodeName = "";
                        //根据流程实例id获取最新待办任务列表
                        List<HistoricTaskInstance> historicTaskList = historyService.createHistoricTaskInstanceQuery()
                                .processInstanceId(item.getId())
                                .unfinished()
                                .orderByHistoricActivityInstanceStartTime()
                                .desc()
                                .list();
                        HistoricTaskInstance historicTaskInstance = null;
                        if (CollectionUtil.isNotEmpty(historicTaskList)) {
                            historicTaskInstance = historicTaskList.get(0);
                            currentNodeName = historicTaskInstance.getName();
                        }
                        List<AssigneeVO> nowAssigneeList = getNowAssigneeList(item.getId());
                        return ProcessDetailVO.builder()
                                .assigneeUserNickList(nowAssigneeList.stream().map(AssigneeVO::getUserNickName).collect(Collectors.toList()))
                                .processType(item.getProcessDefinitionName())
                                .createUserName(item.getStartUserId())
                                .createUserId(Long.valueOf(camundaProvider.getUserId(item.getStartUserId()).getData()))
                                .createUserNickName(camundaProvider.getUserNickname(item.getStartUserId()).getData())
                                .processDefinitionKey(item.getProcessDefinitionKey())
                                .processInstanceId(item.getId())
                                .processCompleted("COMPLETED".equals(item.getState()))
                                .businessKey(item.getBusinessKey())
                                .createTime(LocalDateTime.ofInstant(item.getStartTime().toInstant(), ZoneId.systemDefault()))
                                .endTime(item.getEndTime() == null ? null : LocalDateTime.ofInstant(item.getEndTime().toInstant(), ZoneId.systemDefault()))
                                .currentNodeName(currentNodeName)
                                .dataId(processService.getVariableValue(item.getId(), "dataId"))
                                .variables(historicTaskInstance != null ? getVariablesByTaskId(historicTaskInstance.getId()) : new HashMap<>(16))
                                .build();
                    }))
                    .collect(Collectors.toList());
            for (Future<ProcessDetailVO> future : futureList) {
                try {
                    voList.add(future.get());
                } catch (InterruptedException | ExecutionException e) {
                    log.error("Error while getting task results", e);
                    e.printStackTrace();
                }
            }

            // 关闭线程池
            executor.shutdown();
        }
        iPage.setTotal(totalCount);
        iPage.setRecords(voList);
        return new PageResult<>(iPage);
    }

十、查询抄送给我的任务

关于抄送的实现参考的是:JeecgBoot集成camunda实现抄送功能_camunda 抄送-CSDN博客

抄送代码

通过配置任务监听器的方式,实现在某个节点后抄送他人,这里被抄送人是用流程变量来存储的,不同的流程可以有不同的被抄送人流程变量名,通过自己建立一个中间表act_cc_variable来维护

@Slf4j
@Component
public class CcTaskListener implements TaskListener {

    @Resource
    private IActCcVariableService actCcVariableService;

    @Override
    public void notify(DelegateTask delegateTask) {
        // 获取所有流程变量
        Map<String, Object> variables = delegateTask.getVariables();
        // 获取流程定义 ID
        String processDefinitionId = delegateTask.getProcessDefinitionId();
        // 获取流程定义 KEY
        String processDefinitionKey = StrUtil.isNotEmpty(processDefinitionId) ? processDefinitionId.substring(0, processDefinitionId.indexOf(":")) : "";
        // 获取节点 ID
        String actId = delegateTask.getTaskDefinitionKey();

        //查 流程抄送变量表 获取抄送人变量名
        ActCcVariable variable = actCcVariableService.getOne(new LambdaUpdateWrapper<ActCcVariable>()
                .eq(ActCcVariable::getActId, actId)
                .eq(ActCcVariable::getProcDefKey, processDefinitionKey));
        if (variable == null) {
            log.error("未找到抄送人变量,processDefinitionKey:{},actId:{}", processDefinitionKey, actId);
            return;
        }
        //抄送人变量名
        String variableName = variable.getVariableName();

        //获取抄送人变量值
        String ccVariableValue = StrUtil.isEmpty(String.valueOf(variables.get(variableName))) ?
                variable.getVariableValue() : String.valueOf(variables.get(variableName));

        if (StrUtil.isEmpty(ccVariableValue)) {
            log.error("抄送人变量值为空,processDefinitionId:{},actId:{}", processDefinitionKey, actId);
        }

        //抄送
        String[] copyUsers = ccVariableValue.split(",");
        for (String user : copyUsers) {
            //删除存在的抄送,避免重复抄送
            delegateTask.deleteGroupIdentityLink(user, "cc");
            delegateTask.addUserIdentityLink(user, "cc");
        }

        log.info("抄送成功, taskId:{}, copyUsers:{}", delegateTask.getId(), Arrays.toString(copyUsers));
    }
}

表结构参考

在这里插入图片描述

查询抄送给我的任务

抄送的数据对应的数据表是ACT_HI_IDENTITYLINK

直接看代码:

public PageResult<TaskEntityVO> getCcTasksByAssignee(Integer pageNum, Integer pageSize, String processDefinitionKey) {

        // 获取当前登录用户
        String assignee = getCurrentCamundaUid();
        // 计算起始索引
        int startIndex = (pageNum - 1) * pageSize;
        long count = StrUtil.isNotEmpty(processDefinitionKey) ?
                historyService.createHistoricIdentityLinkLogQuery().userId(assignee).type("cc").processDefinitionKey(processDefinitionKey).count() :
                historyService.createHistoricIdentityLinkLogQuery().userId(assignee).type("cc").count();
        IPage<TaskEntityVO> iPage = new Page<>(pageNum, pageSize);
        List<TaskEntityVO> list = new ArrayList<>();
        if (count > 0) {
            List<HistoricIdentityLinkLog> identityLinkLogs = StrUtil.isNotEmpty(processDefinitionKey) ?
                    historyService.createHistoricIdentityLinkLogQuery().userId(assignee).type("cc").processDefinitionKey(processDefinitionKey).listPage(startIndex, pageSize) :
                    historyService.createHistoricIdentityLinkLogQuery().userId(assignee).type("cc").listPage(startIndex, pageSize);
            if (CollectionUtil.isNotEmpty(identityLinkLogs)) {
                ThreadPoolExecutor executor = new ThreadPoolExecutor(
                        // 核心线程数
                        5,
                        // 最大线程数
                        10,
                        // 空闲线程的存活时间
                        1,
                        // 存活时间单位
                        TimeUnit.MINUTES,
                        // 任务队列,队列大小为100
                        new ArrayBlockingQueue<>(100)
                );
                List<Future<TaskEntityVO>> futureList = identityLinkLogs
                        .stream()
                        .map(historicIdentityLinkLog -> executor.submit(() -> {
                            Task task = getTaskByIdWithoutAssert(historicIdentityLinkLog.getTaskId());
                            if (task != null) {
                                //根据taskId获取流程实例
                                HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                                        .processInstanceId(task.getProcessInstanceId())
                                        .singleResult();
                                ActSysUser startActSysUser = getOneActSysUser(historicProcessInstance.getStartUserId());
                                ActSysUser actSysUser = getOneActSysUser(task.getAssignee());
                                String businessKey = getBusinessKeyByTaskId(task.getId());
                                return TaskEntityVO.builder()
                                        .id(task.getId())
                                        .name(replaceTaskNameAssignee(task.getProcessInstanceId(), task.getAssignee(), task.getId()))
                                        .nodeName(task.getName())
                                        .assignee(task.getAssignee())
                                        .userId(actSysUser != null ? Long.valueOf(actSysUser.getUserId()) : null)
                                        .userNickName(actSysUser != null ? actSysUser.getLastName() + actSysUser.getFirstName() : "")
                                        .startAssignee(historicProcessInstance.getStartUserId())
                                        .startUserId(startActSysUser != null ? Long.valueOf(startActSysUser.getUserId()) : null)
                                        .startUserNickName(startActSysUser != null ? startActSysUser.getLastName() + startActSysUser.getFirstName() : "")
                                        .processDefinitionKey(processService.getProcessDefinitionKey(task.getProcessDefinitionId()))
                                        .processType(processService.getProcessType(processService.getProcessDefinitionKey(task.getProcessDefinitionId())))
                                        .processInstanceId(task.getProcessInstanceId())
                                        .executionId(task.getExecutionId())
                                        .businessKey(businessKey)
                                        .flowTableRoute(getFlowTableRouteByBusinessKey(businessKey))
                                        .dataId(processService.getVariableValue(task.getProcessInstanceId(), "dataId"))
                                        .editFlag("0")
                                        .createTime(LocalDateTime.ofInstant(task.getCreateTime().toInstant(), ZoneId.systemDefault()))
                                        .processCompleted(processInstanceMapper.completed(task.getProcessInstanceId()))
                                        .isCcRead(false)
                                        .build();
                            } else {
                                //根据taskId获取流程实例
                                HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                                        .processInstanceId(historicIdentityLinkLog.getRootProcessInstanceId())
                                        .singleResult();
                                ActSysUser startActSysUser = getOneActSysUser(historicProcessInstance.getStartUserId());
                                HistoricTaskInstance task1 = getHistoricTaskByIdWithoutAssert(historicIdentityLinkLog.getTaskId());
                                ActSysUser actSysUser = getOneActSysUser(task1.getAssignee());
                                String businessKey = getBusinessKeyByTaskId(task1.getId());
                                return TaskEntityVO.builder()
                                        .id(task1.getId())
                                        .name(replaceTaskNameAssignee(task1.getProcessInstanceId(), task1.getAssignee(), task1.getId()))
                                        .nodeName(task1.getName())
                                        .assignee(task1.getAssignee())
                                        .userId(actSysUser != null ? Long.valueOf(actSysUser.getUserId()) : null)
                                        .userNickName(actSysUser != null ? actSysUser.getLastName() + actSysUser.getFirstName() : "")
                                        .startAssignee(historicProcessInstance.getStartUserId())
                                        .startUserId(startActSysUser != null ? Long.valueOf(startActSysUser.getUserId()) : null)
                                        .startUserNickName(startActSysUser != null ? startActSysUser.getLastName() + startActSysUser.getFirstName() : "")
                                        .processDefinitionKey(processService.getProcessDefinitionKey(task1.getProcessDefinitionId()))
                                        .processType(processService.getProcessType(processService.getProcessDefinitionKey(task1.getProcessDefinitionId())))
                                        .processInstanceId(task1.getProcessInstanceId())
                                        .executionId(task1.getExecutionId())
                                        .businessKey(businessKey)
                                        .flowTableRoute(getFlowTableRouteByBusinessKey(businessKey))
                                        .dataId(processService.getVariableValue(task1.getProcessInstanceId(), "dataId"))
                                        .editFlag("0")
                                        .createTime(LocalDateTime.ofInstant(task1.getStartTime().toInstant(), ZoneId.systemDefault()))
                                        .processCompleted(processInstanceMapper.completed(task1.getProcessInstanceId()))
                                        .isCcRead(false)
                                        .build();
                            }
                        })).collect(Collectors.toList());

                for (Future<TaskEntityVO> future : futureList) {
                    try {
                        list.add(future.get());
                    } catch (InterruptedException | ExecutionException e) {
                        log.error("Error while getting task results", e);
                        e.printStackTrace();
                    }
                }

                // 关闭线程池
                executor.shutdown();
            }

        }
        iPage.setTotal(count);
        iPage.setRecords(list);
        return new PageResult<>(iPage);
    }

十一、审批(退回)任务

这里我将审批和退回写成了一个接口,原因是我实现退回的方式是用的画图连线的方式【那时候我没了解到camunda里自带有驳回的api可以用(后面会讲)】

camunda相关 api

//新增评论
Comment comment = taskService.createComment(taskId, processInstanceId, comment;
//完成任务
taskService.complete(taskId, variables);

完整代码

public Boolean completeTask(CompleteTaskDTO completeTaskDTO) {
    //根据任务id查询任务
    Task task = getTaskById(completeTaskDTO.getTaskId());
    //判断是否 当前用户不是任务的操作人
    //根据当前操作人id 获取 当前用户对应的assignee
    Long userid = SecurityUtils.getLoginUser().getUserid() == null ?
            Long.valueOf(ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID)) : SecurityUtils.getLoginUser().getUserid();
    ActSysUser actSysUser = actSysUserService.getOne(new LambdaUpdateWrapper<ActSysUser>().eq(ActSysUser::getUserId, String.valueOf(userid)));
    BaseAssert.isNull(actSysUser, "camunda用户不存在");
    BaseAssert.isTrue(actSysUser != null && !task.getAssignee().equals(actSysUser.getCamundaUserId()), "当前用户不是任务的操作人");


    //用户认证
    log.info("camunda_uid:{}", actSysUser.getCamundaUserId());
    identityService.setAuthenticatedUserId(actSysUser.getCamundaUserId());

    //审批
    Map<String, Object> variables = new HashMap<>(16);
    //设置流程变量
    if (CollectionUtil.isNotEmpty(completeTaskDTO.getVariables())) {
        completeTaskDTO.getVariables().forEach((name, variableDTO) -> {
            Object value = ConvertUtils.convertValue(variableDTO.getValue(), variableDTO.getType());
            variables.put(name, value);
        });
    }
    //如果是退回
    if (completeTaskDTO.getPassed() != null && !completeTaskDTO.getPassed()) {
        //根据当前节点和流程定义key获取 对应退回类型的参数名、参数值
        //任务的流程定义key
        String processDefinitionKey = StrUtil.isNotEmpty(task.getProcessDefinitionId()) ? task.getProcessDefinitionId().substring(0, task.getProcessDefinitionId().indexOf(":")) : "";
        String returnType = getReturnTypeByProcDefKey(processDefinitionKey, task.getTaskDefinitionKey());
        log.info("数据库退回类型:{}, 传入的退回类型:{}", returnType, completeTaskDTO.getReturnType());
        BaseAssert.isTrue(StrUtil.isEmpty(returnType), "该流程不支持退回,请资讯管理员");
        boolean findVariable = false;
        String[] returnTypes = returnType.split(",");
        //判断是否数据库里有这个传入的退回类型
        for (String returnType1 : returnTypes) {
            if (returnType1.equals(String.valueOf(completeTaskDTO.getReturnType()))) {
                findVariable = true;
                break;
            }
        }
        BaseAssert.isFalse(findVariable, "该流程不支持该退回类型,请资讯管理员");
        //获取返回类型对应的参数名、参数值并设置进流程变量
        ActReturnVariable returnVariable = actReturnVariableService.getOne(new LambdaQueryWrapper<ActReturnVariable>()
                .eq(ActReturnVariable::getReturnType, completeTaskDTO.getReturnType())
                .eq(ActReturnVariable::getProcDefKey, task.getProcessDefinitionId().substring(0, task.getProcessDefinitionId().indexOf(":")))
                .eq(ActReturnVariable::getActId, task.getTaskDefinitionKey()));
        if (returnVariable != null) {
            Object value = ConvertUtils.convertValue(returnVariable.getVariableValue(), returnVariable.getVariableType());
            variables.put(returnVariable.getVariableName(), value);
        }
    }
    try {
        Comment comment1 = taskService.createComment(completeTaskDTO.getTaskId(), task.getProcessInstanceId(), completeTaskDTO.getComment());
        taskService.complete(completeTaskDTO.getTaskId(), variables);
        log.info("审批任务成功,taskId:{}, passed:{}, variables:{}, comment1的id:{}, comment1:{}", completeTaskDTO.getTaskId(), completeTaskDTO.getPassed(), variables, comment1.getId(), comment1.getFullMessage());
        return true;
    } catch (Exception e) {
        log.error("审批任务失败", e);
        throw new ServiceException("审批任务失败");
    }
}
public class ConvertUtils {
    /**
     * 转化流程变量类型
     *
     * @param value
     * @param type
     * @return
     */
    public static Object convertValue(Object value, String type) {
        if (value == null) {
            return null;
        }

        // 处理 List 类型
        if (value instanceof List) {
            List<?> valueList = (List<?>) value;
            List<Object> convertedList = new ArrayList<>();
            for (Object item : valueList) {
                // 对每个元素进行转换
                convertedList.add(convertValue(item, type));
            }
            // 返回转换后的列表
            return convertedList;
        }

        switch (type) {
            case "Integer":
                return convertToInteger(value);
            case "Long":
                return convertToLong(value);
            case "Double":
                return convertToDouble(value);
            case "Boolean":
                return convertToBoolean(value);
            case "Date":
                return parseDate(value.toString());
            case "String":
            default:
                return value.toString();
        }
    }

    private static Integer convertToInteger(Object value) {
        if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            return Integer.parseInt((String) value);
        }
        throw new IllegalArgumentException("Cannot convert value to Integer: " + value);
    }

    private static Long convertToLong(Object value) {
        if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof String) {
            return Long.parseLong((String) value);
        }
        throw new IllegalArgumentException("Cannot convert value to Long: " + value);
    }

    private static Double convertToDouble(Object value) {
        if (value instanceof Number) {
            return ((Number) value).doubleValue();
        } else if (value instanceof String) {
            return Double.parseDouble((String) value);
        }
        throw new IllegalArgumentException("Cannot convert value to Double: " + value);
    }

    private static Boolean convertToBoolean(Object value) {
        if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof String) {
            return Boolean.parseBoolean((String) value);
        }
        throw new IllegalArgumentException("Cannot convert value to Boolean: " + value);
    }

    /**
     * 日期转化
     *
     * @param value
     * @return
     */
    public static Date parseDate(String value) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        try {
            return dateFormat.parse(value);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

这里退回的逻辑是:

通过连线 + 条件的方式,当判断退回条件流程变量等于某个值,则走退回的路线。退回变量名、退回变量值、退回类型等由中间表act_return_variable来维护,中间表表结构和上面抄送的中间表类似,如下图所示

在这里插入图片描述

十二、委托任务

委托 和 转办 的区别:

委托是自己的任务给别人,别人审批完后,任务还会回到自己这里,需要继续审批;

转办则是别人审批完后,不会回到自己这里

camunda 相关api

taskService.delegateTask(taskId, userId);

完整代码

public Boolean delegateTask(DelegateTaskDTO delegateTaskDTO) {
        //根据任务id查询任务
        getTaskById(delegateTaskDTO.getTaskId());
        //查询是否存在该被委托人
        ActSysUser actSysUser = actSysUserService.getOne(new LambdaQueryWrapper<ActSysUser>().eq(ActSysUser::getCamundaUserId, delegateTaskDTO.getCamundaUserId()));
        BaseAssert.isNull(actSysUser, "被委托人不存在");
        //委托
        taskService.delegateTask(delegateTaskDTO.getTaskId(), actSysUser.getCamundaUserId());
        return true;
}

十三、转办任务

camunda 相关api

taskService.setAssignee(taskId, userId);

完整代码

public Boolean transferTask(TransferTaskDTO transferTaskDTO) {
        //根据任务id查询任务
        getTaskById(transferTaskDTO.getTaskId());
        //查询是否存在该被转办人
        ActSysUser actSysUser = actSysUserService.getOne(new LambdaQueryWrapper<ActSysUser>().eq(ActSysUser::getUserId, String.valueOf(transferTaskDTO.getUserId())));
        BaseAssert.isNull(actSysUser, "被转办人不存在");

        //用户认证
        log.info("camunda_uid:{}", actSysUser.getCamundaUserId());
        identityService.setAuthenticatedUserId(actSysUser.getCamundaUserId());

        //设置自定义流程变量 taskCustomParam 为原操作者,且只在第一次转办的时候设置
        if (CollectionUtil.isNotEmpty(transferTaskDTO.getVariables())) {
            //审批
            Map<String, Object> variables = new HashMap<>(16);
            //设置流程变量
            if (CollectionUtil.isNotEmpty(transferTaskDTO.getVariables())) {
                transferTaskDTO.getVariables().forEach((name, variableDTO) -> {
                    Object value = ConvertUtils.convertValue(variableDTO.getValue(), variableDTO.getType());
                    variables.put(name, value);
                });
            }
            // 获取任务的流程实例ID
            String processInstanceId = taskService.createTaskQuery()
                    .taskId(transferTaskDTO.getTaskId())
                    .singleResult()
                    .getProcessInstanceId();
            // 更新流程变量
            if (StringUtils.isEmpty(runtimeService.getVariable(processInstanceId, transferTaskDTO.getTaskId()))) {
                runtimeService.setVariables(processInstanceId, variables);
            }
        }
        //转办
        taskService.setAssignee(transferTaskDTO.getTaskId(), actSysUser.getCamundaUserId());
        return true;
    }

十四、添加评论

camunda 相关api

Comment comment = taskService.createComment(taskId,  comment);

完整代码

public Boolean addComment(String taskId, String comment) {
    try {

        //camunda用户认证
        String camundaUid = getCurrentCamundaUid();
        log.info("camunda_uid:{}", camundaUid);
        identityService.setAuthenticatedUserId(camundaUid);

        //获取任务
        Task task = getTaskByIdWithoutAssert(taskId);
        BaseAssert.isNull(task, "任务不存在");
        Comment comment1 = taskService.createComment(taskId, task.getProcessInstanceId(), comment);
        log.info("添加评论成功,taskId:{}, comment1的id:{}, comment1:{}", taskId, comment1.getId(), comment1.getFullMessage());
        return true;
    } catch (Exception e) {
        log.error("添加评论成功失败", e);
        throw new ServiceException("添加评论成功失败");
    }
}

十五、查看流程实例评论列表

camunda 相关api

List<Comment> comments = taskService.getTaskComments(taskId);

完整代码

public List<CommentVO> getCommentsByProcessInstanceId(String processInstanceId, LocalDateTime startTime, LocalDateTime endTime) {
        List<CommentVO> comments = new ArrayList<>();

        // 查询当前未完成的任务
        List<Task> activeTasks = taskService.createTaskQuery()
                .processInstanceId(processInstanceId)
                .list();

        // 查询已完成的任务
        List<HistoricTaskInstance> completedTasks = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .finished()
                .list();

        // 获取当前未完成任务的评论
        for (Task task : activeTasks) {
            List<Comment> taskComments = taskService.getTaskComments(task.getId());
            String taskName = task.getName();
            for (Comment comment : taskComments) {
                LocalDateTime createTime = LocalDateTime.ofInstant(comment.getTime().toInstant(), ZoneId.systemDefault());
                if (isCreateTimeOutOfRange(createTime, startTime, endTime)) {
                    continue;
                }
                comments.add(convertToCommentVO(comment, taskName));
            }
        }

        // 获取已完成任务的评论
        for (HistoricTaskInstance historicTask : completedTasks) {
            List<Comment> historicComments = taskService.getTaskComments(historicTask.getId());
            String taskName = historicTask.getName();
            for (Comment comment : historicComments) {
                LocalDateTime createTime = LocalDateTime.ofInstant(comment.getTime().toInstant(), ZoneId.systemDefault());
                if (isCreateTimeOutOfRange(createTime, startTime, endTime)) {
                    continue;
                }
                comments.add(convertToCommentVO(comment, taskName));
            }
        }

        // 按时间倒序排列
        comments.sort(Comparator.comparing(CommentVO::getTime).reversed());

        return comments;
    }

十六、驳回流程实例

这里的驳回和前面我们用画图连线+条件的方式有所不同,这里是camunda自带的api,效果是修改流程实例到某个节点前或节点后的位置。

相关api:

//驳回
runtimeService.createProcessInstanceModification(processInstanceId)
    .cancelActivityInstance(activityId)
    .setAnnotation("驳回")
    .startBeforeActivity(targetNodeId)
    .execute();

完整代码

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean rejectProcessInstance(RejectInstanceDTO rejectInstanceDTO) {
        //获取当前camunda用户
        String camundaUid = getCurrentCamundaUid();
        log.info("camunda_uid:{}", camundaUid);

        ActivityInstance activity = runtimeService.getActivityInstance(rejectInstanceDTO.getProcessInstanceId());
        BaseAssert.isNull(activity, "获取流程节点信息异常");

        //校验流程实例是否可操作
        HistoricProcessInstance processInstance = checkProcessInstance(rejectInstanceDTO.getProcessInstanceId(), camundaUid);

        //判断驳回的目标节点是否是用户任务节点
        BpmnModelInstance modelInstance = repositoryService.getBpmnModelInstance(processInstance.getProcessDefinitionId());
        BaseAssert.isNull(modelInstance, "获取流程模型信息异常");
        FlowNode flowNode = modelInstance.getModelElementById(activity.getChildActivityInstances()[0].getActivityId());
        BaseAssert.isTrue(!(flowNode instanceof UserTask), "驳回目标节点非用户任务节点,无法驳回");

        //驳回
        runtimeService.createProcessInstanceModification(rejectInstanceDTO.getProcessInstanceId())
                .cancelActivityInstance(activity.getId())
                .setAnnotation("驳回")
                .startBeforeActivity(rejectInstanceDTO.getTargetNodeId())
                .execute();
        //添加意见
        addCommentByProcessInstanceId(rejectInstanceDTO.getProcessInstanceId(), rejectInstanceDTO.getComment());
        return true;
    }

十七、强制归档流程实例

同十六的原理一样,利用camunda的api,强制修改流程实例走到结束节点前,实现强制归档的效果

完整代码

@Override
@Transactional(rollbackFor = Exception.class)
public Boolean forceEndProcessInstance(ForceEndInstanceDTO forceEndInstanceDTO) {
    //获取当前camunda用户
    String camundaUid = getCurrentCamundaUid();
    log.info("camunda_uid:{}", camundaUid);

    ActivityInstance activity = runtimeService.getActivityInstance(forceEndInstanceDTO.getProcessInstanceId());
    BaseAssert.isNull(activity, "获取流程节点信息异常");

    //校验流程实例是否可操作
    HistoricProcessInstance checkProcessInstance = checkProcessInstance(forceEndInstanceDTO.getProcessInstanceId(), camundaUid);

    //获取流程的结束节点
    //获取BPMN模型实例
    BpmnModelInstance modelInstance = repositoryService.getBpmnModelInstance(checkProcessInstance.getProcessDefinitionId());

    //查找所有的结束事件节点
    List<EndEvent> endEvents = new ArrayList<>();
    modelInstance.getModelElementsByType(EndEvent.class).forEach(endEvents::add);

    //确保只有一个结束事件节点
    BaseAssert.isTrue(endEvents.size() > 1, "流程有多个结束节点,无法强制归档");

    //获取唯一的结束事件节点
    EndEvent endEvent = endEvents.get(0);

    //获取结束事件节点的ID
    String endEventId = endEvent.getId();

    //添加意见
    addCommentByProcessInstanceId(forceEndInstanceDTO.getProcessInstanceId(), forceEndInstanceDTO.getComment());

    //强制归档
    runtimeService.createProcessInstanceModification(forceEndInstanceDTO.getProcessInstanceId())
            .cancelActivityInstance(activity.getId())
            .setAnnotation("强制归档")
            .startBeforeActivity(endEventId)
            .execute();

    return true;
}

十八、删除流程定义

使用camunda的删除流程定义api,可以将某流程定义关联的所有表数据一同删除【主要用于删除历史脏数据时使用】

相关api:

repositoryService.deleteProcessDefinition(processDefinitionId, true);

上面第二个参数的意思就是传true时会将历史数据一同删除,传false则只删除流程定义

完整代码

public Boolean deleteProcess(String processDefinitionKey) {
    List<ProcessDefinition> processDefinitionList = getProcessDefinitionList(processDefinitionKey);

    // 删除流程
    for (ProcessDefinition processDefinition : processDefinitionList) {
        repositoryService.deleteProcessDefinition(processDefinition.getId(), true);
    }
    log.info("流程已删除: {}", processDefinitionKey);

    return true;
}

十九、挂起流程定义

挂起 流程定义后,不能发起该流程,只有 重新激活 后才能发起

相关api:

repositoryService.suspendProcessDefinitionByKey(processDefinitionKey, true, null);

完整代码

public Boolean suspendProcess(String processDefinitionKey) {
    List<ProcessDefinition> processDefinitionList = getProcessDefinitionList(processDefinitionKey);

    // 挂起流程
    for (ProcessDefinition processDefinition : processDefinitionList) {
        repositoryService.suspendProcessDefinitionByKey(processDefinition.getKey(), true, null);
    }
    log.info("流程已挂起: {}", processDefinitionKey);

    return true;
}

二十、重新激活流程

camunda 相关api

repositoryService.activateProcessDefinitionByKey(processDefinitionKey, true, null);

完整代码

public Boolean activateProcess(String processDefinitionKey) {
    List<ProcessDefinition> processDefinitionList = getProcessDefinitionList(processDefinitionKey);

    // 激活流程
    for (ProcessDefinition processDefinition : processDefinitionList) {
        // 检查当前流程是否处于挂起状态再进行激活
        if (processDefinition.isSuspended()) {
            repositoryService.activateProcessDefinitionByKey(processDefinition.getKey(), true, null);
        }
    }
    log.info("流程已激活: {}", processDefinitionKey);

    return true;
}

二十一、用接口测试走流程

1.流程图

画一个简单的测试流程图,如下所示

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0op68w6" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.26.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.19.0">
  <bpmn:process id="Test_5_easy_demo" name="测试简单流程" isExecutable="true">
    <bpmn:startEvent id="StartEvent_1" name="start" camunda:initiator="starter">
      <bpmn:outgoing>Flow_1fvh4gl</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:userTask id="Activity_01pcziw" name="发起" camunda:assignee="${starter}">
      <bpmn:extensionElements>
        <camunda:formData />
      </bpmn:extensionElements>
      <bpmn:incoming>Flow_1fvh4gl</bpmn:incoming>
      <bpmn:outgoing>Flow_10yi95x</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:sequenceFlow id="Flow_1fvh4gl" sourceRef="StartEvent_1" targetRef="Activity_01pcziw" />
    <bpmn:userTask id="Activity_0wgrw1d" name="admin审批" camunda:assignee="admin">
      <bpmn:extensionElements>
        <camunda:formData />
      </bpmn:extensionElements>
      <bpmn:incoming>Flow_10yi95x</bpmn:incoming>
      <bpmn:outgoing>Flow_0v1ahgg</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:sequenceFlow id="Flow_10yi95x" sourceRef="Activity_01pcziw" targetRef="Activity_0wgrw1d" />
    <bpmn:endEvent id="Event_1iz7hri" name="end">
      <bpmn:extensionElements />
      <bpmn:incoming>Flow_0v1ahgg</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="Flow_0v1ahgg" sourceRef="Activity_0wgrw1d" targetRef="Event_1iz7hri" />
  </bpmn:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Test_5_easy_demo">
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
        <dc:Bounds x="192" y="112" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="200" y="82" width="23" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="BPMNShape_13xrbqm" bpmnElement="Activity_01pcziw">
        <dc:Bounds x="160" y="210" width="100" height="80" />
        <bpmndi:BPMNLabel />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="BPMNShape_043qxef" bpmnElement="Activity_0wgrw1d">
        <dc:Bounds x="160" y="340" width="100" height="80" />
        <bpmndi:BPMNLabel />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_1iz7hri_di" bpmnElement="Event_1iz7hri">
        <dc:Bounds x="192" y="482" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="201" y="525" width="19" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="Flow_1fvh4gl_di" bpmnElement="Flow_1fvh4gl">
        <di:waypoint x="210" y="148" />
        <di:waypoint x="210" y="210" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_10yi95x_di" bpmnElement="Flow_10yi95x">
        <di:waypoint x="210" y="290" />
        <di:waypoint x="210" y="340" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0v1ahgg_di" bpmnElement="Flow_0v1ahgg">
        <di:waypoint x="210" y="420" />
        <di:waypoint x="210" y="482" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

2.重启项目,检查流程部署

重启项目后访问bpm平台检查流程是否正常部署,如下所示

在这里插入图片描述

3.启动流程

启动流程接口 实现

传参示例

{
  "businessKey": "test20241210",
  "processDefinitionKey": "Test_5_easy_demo",
  "variables": {
    "t1": {
      "type": "String",
      "value": "测试流程变量"
    }
  }
}

在这里插入图片描述

调用成功后,我们直接去bpm平台看,登录刚刚调用接口的用户,查看任务列表

可以看到,正常启动了流程,当前节点来到了发起节点

在这里插入图片描述

4.添加评论

在右上角我们可以看到Add Comment的功能,即添加评论。如下图

在这里插入图片描述

现在我们用添加评论 接口来添加试试

传参示例

{
  "comment": "测试加评论",
  "taskId": "547787f1-b6aa-11ef-a2f3-0242ef616bb4"
}

taskId是任务id,如下图可以看到

在这里插入图片描述

调用成功后,我们再返回bpm查看

在这里插入图片描述

可以看到刚刚加的评论

5.审批任务

一般来说,我们添加评论都是直接在审批的时候同时添加的,所以这里审批接口我也将添加评论的逻辑整合进去了。

传参示例

{
  "comment": "通过",
  "passed": true,
  "taskId": "547787f1-b6aa-11ef-a2f3-0242ef616bb4"
}

调用成功后,我们可以看到流程已经走到了下个节点

在这里插入图片描述

6.驳回流程实例到发起节点

这里我没有画退回的路线,正好我们用驳回接口来测试驳回效果

传参示例

{
  "comment": "测试驳回",
  "processInstanceId": "5471e399-b6aa-11ef-a2f3-0242ef616bb4",
  "targetNodeId": "Activity_01pcziw"
}

processInstanceId是流程实例id,如下图所示

在这里插入图片描述

targetNodeId是目标节点id,如下图所示

在这里插入图片描述

调用成功后,我们返回bpm平台,可以看到流程已经回退到发起节点

在这里插入图片描述

7.走完流程

后面的过程就不细讲了,重复调用审批任务接口,即可走完流程


原文地址:https://blog.csdn.net/m0_52620144/article/details/144373359

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!