本篇讲述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博客
文章目录
- 一、用户同步
- 二、用户认证
- 三、部署流程
- 四、查看可启动流程列表
- 五、启动流程
- 六、查询待办任务
- 七、查询任务详情
- 八、查询已办任务
- 九、查询我发起的流程
- 十、查询抄送给我的任务
- 十一、审批(退回)任务
- 十二、委托任务
- 十三、转办任务
- 十四、添加评论
- 十五、查看流程实例评论列表
- 十六、驳回流程实例
- 十七、强制归档流程实例
- 十八、删除流程定义
- 十九、挂起流程定义
- 二十、重新激活流程
- 二十一、用接口测试走流程
- 1.流程图
- 2.重启项目,检查流程部署
- 3.启动流程
- 4.添加评论
- 5.审批任务
- 6.驳回流程实例到发起节点
- 7.走完流程
一、用户同步
由于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 {@Resourceprivate 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);// 记录失败的用户IDList<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) {// 记录失败的用户IDfailedIds.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;}@Overridepublic 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;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {SecurityContextHolder.remove();}
}
@Configuration
@EnableWebMvc
public class WebMvcConfigOfCamunda implements WebMvcConfigurer {@Resourceprivate 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/"};@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(headerInterceptorOfCamunda()).addPathPatterns(PATTERN);}/*** 自定义请求头拦截器*/@Beanpublic HeaderInterceptorOfCamunda headerInterceptorOfCamunda() {return new HeaderInterceptorOfCamunda(identityService);}@Overridepublic 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_DEPLOYMENT
的NAME_
字段,如果是自动部署的就如下图所示
-
部署外部上传的流程图文件
Deployment deploy = repositoryService.createDeployment().name("部署名").addInputStream(fileName, inputStream).deploy();
inputStream
就是从上传的文件获取的,fileName为资源名,也就是文件名,对应数据库表ACT_GE_BYTEARRAY
的NAME_
字段如下图
-
完整代码
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 {@CleanupInputStream 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,// 任务队列,队列大小为100new 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_PROCDEF
的NAME_
字段
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) {// 获取默认的 ProcessEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取 RuntimeServiceRuntimeService 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);// 获取任务的流程定义 IDString processDefinitionId = task.getProcessDefinitionId();// 获取任务的流程定义 KEY(截取processDefinitionId第一个冒号前面的字符串)String processDefinitionKey = StrUtil.isNotEmpty(processDefinitionId) ? processDefinitionId.substring(0, processDefinitionId.indexOf(":")) : "";// 当前节点 IDString taskDefinitionKey = task.getTaskDefinitionKey();// 获取当前节点 NodeVONodeVO 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;}// 获取任务的流程定义 IDString processDefinitionId = historicTask.getProcessDefinitionId();// 获取任务的流程定义 KEY(截取processDefinitionId第一个冒号前面的字符串)String processDefinitionKey = StrUtil.isNotEmpty(processDefinitionId) ? processDefinitionId.substring(0, processDefinitionId.indexOf(":")) : "";// 获取流程实例idString 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,// 任务队列,队列大小为100new 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,// 任务队列,队列大小为100new 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 {@Resourceprivate IActCcVariableService actCcVariableService;@Overridepublic void notify(DelegateTask delegateTask) {// 获取所有流程变量Map<String, Object> variables = delegateTask.getVariables();// 获取流程定义 IDString processDefinitionId = delegateTask.getProcessDefinitionId();// 获取流程定义 KEYString processDefinitionKey = StrUtil.isNotEmpty(processDefinitionId) ? processDefinitionId.substring(0, processDefinitionId.indexOf(":")) : "";// 获取节点 IDString 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,// 任务队列,队列大小为100new 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 获取 当前用户对应的assigneeLong 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获取 对应退回类型的参数名、参数值//任务的流程定义keyString 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);});}// 获取任务的流程实例IDString 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);//获取结束事件节点的IDString 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.走完流程
后面的过程就不细讲了,重复调用审批任务接口,即可走完流程