详解SpringCloud集成Camunda7.19实现工作流审批(四)
本篇讲述camunda的基础功能如何集成在springboot项目中,用接口去操作走通camunda流程
参考资料:
camunda数据库表结构介绍_camunda 表结构-CSDN博客
blog.csdn.net/QingXu1234/article/details/122439353?spm=1001.2014.3001.5502
文章目录
一、用户同步
由于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_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 { @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_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) {
// 获取默认的 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)!