前言:因为公司有个项目需求要使用到工作流引擎,考查了市面各种的工作流引擎,对比它们之间的优劣势,最后选择Camunda工作流引擎。此前自己对Camunda工作流引擎了解的并不多,所以就记录下自己学习Camunda工作流引擎到springboot项目中整合Camunda工作流引擎使用的过程。
在上一篇文章中已经介绍了Camunda Platform和Modeler创建工作流的玩法了。在本文就将Camunda 应用在生产项目的各种审核流程中,重点是springboot整合Camunda ,并使用Camunda 的各种API。因为camunda本来就是一个轻量级的框架,项目中就主要使用Camunda 的工作流的流程流转和审核,流程的创建和前端bpmn页面流程编辑器部分这里就不介绍使用了。因为在经历几个生产项目,无论是使用flowable或者Camunda 工作流框架,流程bpmn文件都是提前通过其他工具创建好,然后放在项目中加载部署的,项目主要是对应的流程的流转和审核。不多废话,下面就直接进入正题。
一、引入Camunda依赖
springboot引入Camunda要考虑版本兼容性的问题,我的springboot版本2.5.x,所以我引入Camunda的版本是7.16.x,
要看springboot对应Camunda的具体的版本:
https://docs.camunda.org/manual/7.19/user-guide/spring-boot-integration/version-***patibility/
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter</artifactId>
<version>7.16.0</version>
</dependency>
二、在yml配置camunda
camunda的详细配置说明:
https://docs.camunda.org/manual/latest/user-guide/spring-boot-integration/configuration/#camunda-engine-properties
这里就说几个比较重要的配置
camunda.bpm.auto-deployment-enabled :流程是否应该自动部署,默认为true。
(很重要)camunda.bpm.deployment-resource-pattern:自动部署位置。一开始我学习我不理解为什么bpmn文件无论放在那里都能自动部署。
默认位置:
classpath*😗*/*.bpmn,
classpath*😗*/*.bpmn20.xml,
classpath*😗*/*.dmn,
classpath*😗*/*.dmn11.xml,
classpath*😗*/*.cmmn,
classpath*😗*/*.cmmn10.xml,
classpath*😗*/*.cmmn11.xml
camunda.bpm.database.schema-update: 如果应应用自动架构更新,请使用 [true、false、create、create-drop、drop-create] 之一,默认为true。
camunda.bpm.database.type: 底层数据库的类型。可能的值:
h2
、mysql、mariadb、oracle、postgres、mssql、db2。camunda.bpm.admin-user.id: 用户名
camunda.bpm.admin-user.password: 初始化密码
camunda.bpm.admin-user.firstName: 附加(可选)用户属性 ,默认为“id”的值
camunda.bpm.admin-user.lastName: 附加(可选)用户属性 ,默认为“id”的值
camunda.bpm.filter.create:“显示全部”过滤器的名称。如果设置,则会在启动时创建一个显示所有任务的新过滤器。
下面是在实际项目中的配置
# camunda配置
camunda:
bpm:
admin-user:
id: camunda
password: camunda
first-name: admin
filter:
create: All tasks
database:
type: mysql
schema-update: true
auto-deployment-enabled: true
deployment-resource-pattern: classpath:/processes/*.bpmn
三、初始化camunda数据库,执行建表脚本(很重要)
这里需要手动建好camunda相关的表,不提前建好的话,到导致项目中的camunda的相关的bean没办法自动注入到spring容器中,会无法正常启动项目。camunda的相关脚本语句,去官网找到对应版本(此例中Camunda版本为7.16.0)的sql脚本文件:
https://camunda.***/download/
解压后,找到./configuration/sql目录下即可找到sql文件,engine和identity都执行。项目中使用的是mysql数据,所以执行mysql的脚本。
四、camunda数据库表的说明(扩展)
PS: 这一步跟项目无关,但是能加深对camunda的理解,而且对项目中出现问题的排查能提供很好的帮助。
Camunda bpm流程引擎的数据库由多个表组成,表名都以ACT开头,第二部分是说明表用途的两字符标识。而Camunda7.16版本共49张表。
ACT_RE_* : 'RE’表示流程资源存储,这个前缀的表包含了流程定义和流程静态资源(图片,规则等)
(最重要)ACT_RU_* : 'RU’表示流程运行时。 这些运行时的表,包含流程实例,任务,变量,Job等运行中的数据。 Camunda只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录, 这样运行时表的数据量最小,可以最快运行
ACT_ID_* : 'ID’表示组织用户信息,比如用户,组等,
ACT_HI_* : 'HI’表示流程历史记录。 这些表包含历史数据,比如历史流程实例,变量,任务等
ACT_GE_* : ‘GE’表示流程通用数据要记住一个很重要的一个点:流程运行时的数据是存在ACT_RU相关的表上,当流程结束后,该流程相关的数据就会被物理删除掉,能减少很多数据量,提高流程的性能。而想要看相关流程的过程记录,则需要到ACT_HI相关的表上去查找
流程引擎的最核心表是流程定义、流程执行、流程任务、流程变量和事件订阅表。它们之间的关系见下面的UML模型。
下面是项目中排查问题用的比较多,比较重要的几张表的说明
act_id_user(用户表)
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
REV_ | int(11) | NULL | 版本 |
FIRST_ | varchar(255) | NULL | 姓 |
LAST_ | varchar(255) | NULL | 名 |
EMAIL_ | varchar(255) | NULL | 邮件 |
PWD_ | varchar(255) | NULL | 密码 |
SALT_ | varchar(255) | NULL | 盐值 |
LOCK_EXP_TIME_ | datetime | NULL | 锁定过期时间 |
ATTEMPTS_ | int(11) | NULL | 尝试次数 |
PICTURE_ID_ | varchar(64) | NULL | 图片ID |
act_ge_bytearray(二进制数据表)
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
REV_ | int(11) | NULL | 版本 |
NAME_ | varchar(255) | NULL | 名称 |
DEPLOYMENT_ID_ | varchar(64) | NULL | 部署ID |
BYTES_ | longblob | NULL | 字节内容 |
GENERATED_ | tinyint(4) | NULL | 是否系统生成(0用户创建,null系统生成) |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
TYPE_ | int(11) | NULL | 类型 |
CREATE_TIME_ | datetime | NULL | 创建时间 |
ROOT_PROC_INST_ID_ | varchar(64) | NULL | 流程实例根ID |
REMOVAL_TIME_ | datetime | NULL | 删除时间 |
act_re_procdef(流程定义表)
流程定义表,包含所有已部署的流程定义,诸如版本详细信息、资源名称或挂起状态等信息。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
REV_ | int(11) | NULL | 版本 |
CATEGORY_ | varchar(255) | NULL | 流程定义的Namespace分类 |
NAME_ | varchar(255) | NULL | 流程定义名称 |
KEY_ | varchar(255) | 流程定义KEY | |
VERSION_ | int(11) | 流程定义版本号 | |
DEPLOYMENT_ID_ | varchar(64) | NULL | 部署ID |
RESOURCE_NAME_ | varchar(4000) | NULL | 资源名称 |
DGRM_RESOURCE_NAME_ | varchar(4000) | NULL | DGRM资源名称 |
HAS_START_FORM_KEY_ | tinyint(4) | NULL | 是否有启动表单 |
SUSPENSION_STATE_ | int(11) | NULL | 流程挂起 |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
VERSION_TAG_ | varchar(64) | NULL | 版本标签 |
HISTORY_TTL_ | int(11) | NULL | |
STARTABLE_ | tinyint(1) | 是否是可启动流程 |
act_ru_execution(流程运行时表)—最重要
BPMN流程运行时记录表。该表时整个流程引擎的核心表,它包括流程定义、父级执行、当前活动和有关执行状态的不同元数据等信息。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
REV_ | int(11) | NULL | 版本 |
ROOT_PROC_INST_ID_ | varchar(64) | NULL | 流程实例根ID |
PROC_INST_ID_ | varchar(64) | NULL | 流程实例ID |
BUSINESS_KEY_ | varchar(255) | NULL | 业务KEY |
PARENT_ID_ | varchar(64) | NULL | 流程父实例ID |
PROC_DEF_ID_ | varchar(64) | NULL | 流程定义ID |
SUPER_EXEC_ | varchar(64) | NULL | 父流程实例对应的执行 |
SUPER_CASE_EXEC_ | varchar(64) | NULL | 父案例实例对应的执行 |
CASE_INST_ID_ | varchar(64) | NULL | 案例实例ID |
ACT_ID_ | varchar(255) | NULL | 节点ID |
ACT_INST_ID_ | varchar(64) | NULL | 节点实例ID |
IS_ACTIVE_ | tinyint(4) | NULL | 是否激活 |
IS_CONCURRENT_ | tinyint(4) | NULL | 是否并行 |
IS_SCOPE_ | tinyint(4) | NULL | 是否多实例范围 |
IS_EVENT_SCOPE_ | tinyint(4) | NULL | 是否事件多实例范围 |
SUSPENSION_STATE_ | int(11) | NULL | 挂起状态 |
CACHED_ENT_STATE_ | int(11) | NULL | 缓存状态 |
SEQUENCE_COUNTER_ | bigint(20) | NULL | 序列计数器 |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
act_ru_identitylink(流程运行时表)
运行时流程人员表,主要存储当前节点参与者的信息
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
REV_ | int(11) | NULL | 版本 |
GROUP_ID_ | varchar(255) | NULL | 用户组ID |
TYPE_ | varchar(255) | NULL | 类型 |
USER_ID_ | varchar(255) | NULL | 用户ID |
TASK_ID_ | varchar(64) | NULL | 任务ID |
PROC_DEF_ID_ | varchar(64) | NULL | 流程定义ID |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
act_ru_task( 流程运行时任务表)
流程运行时任务表,包含所有正在运行的流程实例的所有打开的任务,包括诸如相应的流程实例、执行以及元数据(如创建时间、办理人或到期时间)等信息。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
REV_ | int(11) | NULL | 版本 |
EXECUTION_ID_ | varchar(64) | NULL | 流程执行ID |
PROC_INST_ID_ | varchar(64) | NULL | 流程实例ID |
PROC_DEF_ID_ | varchar(64) | NULL | 流程定义ID |
CASE_EXECUTION_ID_ | varchar(64) | NULL | 案例执行ID |
CASE_INST_ID_ | varchar(64) | NULL | 案例实例ID |
CASE_DEF_ID_ | varchar(64) | NULL | 案例定义ID |
NAME_ | varchar(255) | NULL | 名称 |
PARENT_TASK_ID_ | varchar(64) | NULL | 父任务ID |
DESCRIPTION_ | varchar(4000) | NULL | 描述 |
TASK_DEF_KEY_ | varchar(255) | NULL | 任务定义KEY |
OWNER_ | varchar(255) | NULL | 委托人 |
ASSIGNEE_ | varchar(255) | NULL | 办理人 |
DELEGATION_ | varchar(64) | NULL | 委托状态 |
PRIORITY_ | int(11) | NULL | 优先级 |
CREATE_TIME_ | datetime | NULL | 创建时间 |
DUE_DATE_ | datetime | NULL | 截止时间 |
FOLLOW_UP_DATE_ | datetime | NULL | 跟踪时间 |
SUSPENSION_STATE_ | int(11) | NULL | 挂起状态 |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
act_hi_***ment(历史流程审批意见表)
历史流程审批意见表,存放历史流程的审批意见。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
TYPE_ | varchar(255) | NULL | 类型(event事件、***ment意见) |
TIME_ | datetime | 时间 | |
USER_ID_ | varchar(255) | NULL | 处理人 |
TASK_ID_ | varchar(64) | NULL | 任务ID |
ROOT_PROC_INST_ID_ | varchar(64) | NULL | 流程实例跟ID |
PROC_INST_ID_ | varchar(64) | NULL | 流程实例ID |
ACTION_ | varchar(255) | NULL | 行为类型 |
MESSAGE_ | varchar(4000) | NULL | 基本内容 |
FULL_MSG_ | longblob | NULL | 全部内容 |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
REMOVAL_TIME_ | datetime | NULL | 移除时间 |
act_hi_detail(历史的流程运行详情表)
历史的流程运行变量详情记录表。流程中产生的变量详细,包括控制流程流转的变量,业务表单中填写的流程需要用到的变量等。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
TYPE_ | varchar(255) | 类型 | |
PROC_DEF_KEY_ | varchar(255) | NULL | 流程定义KEY |
PROC_DEF_ID_ | varchar(64) | NULL | 流程定义ID |
ROOT_PROC_INST_ID_ | varchar(64) | NULL | 流程实例根ID |
PROC_INST_ID_ | varchar(64) | NULL | 流程实例ID |
EXECUTION_ID_ | varchar(64) | NULL | 流程执行ID |
CASE_DEF_KEY_ | varchar(255) | NULL | 案例定义KEY |
CASE_DEF_ID_ | varchar(64) | NULL | 案例定义ID |
CASE_INST_ID_ | varchar(64) | NULL | 案例实例ID |
CASE_EXECUTION_ID_ | varchar(64) | NULL | 案例执行ID |
TASK_ID_ | varchar(64) | NULL | 任务ID |
ACT_INST_ID_ | varchar(64) | NULL | 节点实例ID |
VAR_INST_ID_ | varchar(64) | NULL | 流程变量记录ID |
NAME_ | varchar(255) | 名称 | |
VAR_TYPE_ | varchar(255) | NULL | 变量类型 |
REV_ | int(11) | NULL | 版本 |
TIME_ | datetime | 时间戳 | |
BYTEARRAY_ID_ | varchar(64) | NULL | 二进制数据对应ID |
DOUBLE_ | double | NULL | double类型值 |
LONG_ | bigint(20) | NULL | long类型值 |
TEXT_ | varchar(4000) | NULL | 文本类型值 |
TEXT2_ | varchar(4000) | NULL | 文本类型值2 |
SEQUENCE_COUNTER_ | bigint(20) | NULL | 序列计数器 |
TENANT_ID_ | varchar(64) | NULL | 租户ID |
OPERATION_ID_ | varchar(64) | NULL | |
REMOVAL_TIME_ | datetime | NULL | 移除时间 |
五、编写camunda的工具类。
其实完成前面3步,可以说springboot已经整合camunda成功了,但是一个项目中引入一个框架主要是为了使用,camunda官方提供了很多API,参考官方文档:https://docs.camunda.org/manual/7.18/reference/rest/
但是在camunda不同的流程的审核所 使用的API是大多数是相同的,所以在项目中封装一个工具类提供使用。
java">/**
* camunda 流程工具类
*/
@***ponent
@Slf4j
public class FlowUtil {
private static final FlowUtil util = new FlowUtil();
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@PostConstruct
public void initialize() {
util.repositoryService = repositoryService;
util.runtimeService = runtimeService;
util.taskService = taskService;
util.historyService = historyService;
}
/**
* 部署流程,参数本地bpmn的路径
* @param filePath bpmn路径
* @param name 流程名称
*/
public static void deployment(String filePath,String name){
log.info("开始部署本地审批流程:{},文件地址:{}",name,filePath);
util.repositoryService.createDeployment()
.name(name)
.addClasspathResource(filePath)
.deploy();
log.info("完成部署本地审批流程:{},文件地址:{}",name,filePath);
}
/**
* 启动流程
* @param key 流程标识,需唯一,如果存在相同key则会启动版本号最新的流程
* @param businessKey 业务id
* @param map 流程变量(包含节点审批人及业务判断变量等)
*/
public static void startProcess(String key, Object businessKey, Map<String,Object> map){
log.info("开始启动审批流程:{},业务源:{},流程变量: {}",key,businessKey.toString(), JSONUtil.toJsonStr(JSONUtil.parse(map)));
ProcessInstance processInstance = util.runtimeService.startProcessInstanceByKey(key, businessKey.toString(), map);
log.info("完成启动审批流程:{},业务源:{},流程实例ID:{}",key,businessKey.toString(), processInstance.getId());
}
/**
* 销毁流程
* @param key 流程标识,需唯一,如果存在相同key则会启动版本号最新的流程
* @param businessKey 业务id
* @param reason 销毁原因
* @param reason 审批人
*/
public static void destroyProcess(String key, String businessKey, String reason,String assignee){
//查询待办任务
List<Task> tasks = FlowUtil.getTaskByCandidateUserAndBusinessKey(key, businessKey, assignee);
if(CollUtil.isEmpty(tasks)){
log.info("{}完成审批任务失败:{}--{}",assignee,key,businessKey);
throw new ResultException("暂未到您审批");
}
FlowUtil.claimTask(tasks.get(0).getId(),assignee);
List<Task> taskList = FlowUtil.getTasksByBusinessKey(key, businessKey);
if(CollUtil.isEmpty(taskList)) return;
log.info("开始销毁审批流程:{},业务源:{},流程实例ID:{}:",key,businessKey, taskList.get(0).getProcessInstanceId());
util.runtimeService.deleteProcessInstance(taskList.get(0).getProcessInstanceId(), reason);
log.info("完成销毁审批流程:{},业务源:{},流程实例ID:{}",key,businessKey, taskList.get(0).getProcessInstanceId());
}
/**
* 领取任务
* @param taskId 任务id
* @param candidateUser 候选用户名
*/
public static void claimTask(String taskId,String candidateUser){
Task task = util.taskService.createTaskQuery()
.taskId(taskId)
.taskCandidateUser(candidateUser)
.singleResult();
if(Objects.isNull(task)){
log.info("{}领取审批任务失败:{}",candidateUser,taskId);
throw new ResultException("任务领取失败");
}
util.taskService.claim(taskId,candidateUser);
}
/**
* 完成任务
* @param taskId 任务id
* @param assignee 用户名
*/
public static void ***pleteTask(String taskId,String assignee){
Task task = util.taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assignee)
.singleResult();
if(Objects.isNull(task)){
log.info("{}完成审批任务失败:{}",assignee,taskId);
throw new ResultException("任务完成失败");
}
util.taskService.***plete(taskId);
}
/**
* 领取并完成任务
* @param taskId 任务id
* @param assignee 用户名
*/
public static void claimAnd***pleteTask(String taskId,String assignee){
Task task = util.taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if(Objects.isNull(task)){
log.info("{}审批任务不存在:{}",assignee,taskId);
throw new ResultException("审批任务不存在");
}
if(StrUtil.isNotBlank(task.getAssignee())){
if(!task.getAssignee().equalsIgnoreCase(assignee)){
log.info("{}完成审批任务失败:{}",assignee,taskId);
throw new ResultException("暂未到您审批");
}
util.taskService.***plete(taskId);
return;
}
Task unClaimTask = util.taskService.createTaskQuery()
.taskId(taskId)
.taskCandidateUser(assignee)
.singleResult();
if(Objects.isNull(unClaimTask)){
log.info("{}完成审批任务失败:{}",assignee,taskId);
throw new ResultException("暂未到您审批");
}
util.taskService.claim(taskId,assignee);
util.taskService.***plete(taskId);
}
/**
* 查询待办任务,参数:候选用户、业务id
* @param candidateUser 候选用户
* @param key 流程标识,需唯一,如果存在相同key则会启动版本号最新的流程
* @param businessKey 业务id
* @return
*/
public static List<Task> getTaskByCandidateUserAndBusinessKey(String key,String businessKey,String candidateUser){
return util.taskService.createTaskQuery()
.processInstanceBusinessKey(businessKey)
.processDefinitionKey(key)
.taskCandidateUser(candidateUser)
.list();
}
/**
* 查询待办任务,参数: 业务id
* @param key 流程标识,需唯一,如果存在相同key则会启动版本号最新的流程
* @param businessKey 业务id
* @return
*/
public static List<Task> getTasksByBusinessKey(String key, String businessKey){
return util.taskService.createTaskQuery()
.processDefinitionKey(key)
.processInstanceBusinessKey(businessKey)
.list();
}
/**
* 查询待办任务,参数: 业务id
* @param key 流程标识,需唯一,如果存在相同key则会启动版本号最新的流程
* @param businessKey 业务id
* @param assignee 用户名
* @return
*/
public static boolean claimAnd***pleteTask(String key, String businessKey,String assignee){
//查询待办任务
List<Task> tasks = FlowUtil.getTaskByCandidateUserAndBusinessKey(key, businessKey, assignee);
if(CollUtil.isEmpty(tasks)){
log.info("{}完成审批任务失败:{}--{}",assignee,key,businessKey);
throw new ResultException("暂未到您审批");
}
for (Task task : tasks) {
FlowUtil.claimAnd***pleteTask(task.getId(),assignee);
}
if(CollUtil.isEmpty(FlowUtil.getTasksByBusinessKey(key,businessKey))){
return true;
}else{
return false;
}
}
/**
* 查询历史任务
* @param key
* @param businessKey
* @return
*/
public static List<HistoricTaskInstance> getHistoricTask(String processInstanceId,String key, String businessKey){
return util.historyService.createHistoricTaskInstanceQuery()
.processInstanceBusinessKey(businessKey)
.processInstanceId(processInstanceId)
.processDefinitionKey(key)
.list();
}
/**
* 查询历史实例
* @param key
* @param businessKey
* @return
*/
public static HistoricProcessInstance getLastProcessInstance(String key, String businessKey){
return util.historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(key)
.processInstanceBusinessKey(businessKey)
.orderByProcessInstanceStartTime()
.desc().list().get(0);
}
/**
* 查询任务
* @param key
* @param taskId
* @return
*/
public static List<HistoricIdentityLinkLog> getHistoricIdentityLinkLog(String key, String taskId){
return util.historyService.createHistoricIdentityLinkLogQuery()
.processDefinitionKey(key)
.taskId(taskId)
.list();
}
/**
* 查询任务
* @param taskId
* @return
*/
public static List<IdentityLink> getTaskIdentityLink(String taskId){
return util.taskService.getIdentityLinksForTask(taskId);
}
}
五、模拟生产项目中的功能-费用审核流程
下面是费用审核的流程图
5.1 创建费用审核流程(即创建bpmn文件)
创建流程可以使用Camunda Modeler,具体的使用方法在我上一篇文章中有写到,又不了解的可以移步到:
https://blog.csdn.***/qq798867485/article/details/131439688
下面是流程图的创建和每个节点的设置情况
5.2 把bpmn文件放到项目的processes目录
一定要放在这个目录下,因为之前的yml的配置指定了自动部署的目录,不然无法自动部署。除非你在yml配置中不制动自动部署的目录。
5.3 创建费用审核流程
FlowUtil.startProcess(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId(),params);
这个方法就是 创建费用审核流程
@Override
@Transactional(rollbackFor = Exception.class)
public void submit(Expense expense) {
dao.insert(expense);
List<User> userList = userService.listAll(new UserQueryBo());
Set<String> ***panyUsers = userList.stream().filter(x -> x.getUserRole() == 1)
.map(item -> item.getId().toString()).collect(Collectors.toSet());
Set<String> groupUsers = userList.stream().filter(x -> x.getUserRole() == 2)
.map(item -> item.getId().toString()).collect(Collectors.toSet());
Set<String> headUsers = userList.stream().filter(x -> x.getUserRole() == 3)
.map(item -> item.getId().toString()).collect(Collectors.toSet());
Map<String,Object> params = new HashMap<>();
params.put("***panyUsers", ***panyUsers);
params.put("groupUsers",groupUsers);
params.put("headUsers",headUsers);
FlowUtil.startProcess(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId(),params);
}
5.4 审核费用审核流程
##查看这个审核节点是否为审核流程最后的一个节点
FlowUtil.claimAnd***pleteTask(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId().toString(),expenseQueryBo.getApprovalId().toString())
##驳回
FlowUtil.destroyProcess(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId().toString(),“不想通过”,expenseQueryBo.getApprovalId().toString())
@Override
@Transactional(rollbackFor = Exception.class)
public void approval(Expense expense, ExpenseQueryBo expenseQueryBo) {
if(expenseQueryBo.getApprovalStatus() == 2){
//通过
if(FlowUtil.claimAnd***pleteTask(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId().toString(),expenseQueryBo.getApprovalId().toString())){
expense.setApprovalStatus(2);
}else {
expense.setApprovalStatus(1);
}
}else {
//驳回
FlowUtil.destroyProcess(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId().toString(),"不想通过",expenseQueryBo.getApprovalId().toString());
expense.setApprovalStatus(3);
}
this.updateById(expense);
}
六、总结(代码)
学习camunda最主要是理解流程的流转和相关表的结构。上面只是我简单模拟一个费用审核流程的样例,实际生产中的业务代码比这个要复杂,但是核心的camunda的工具类的使用是不变的,变的只是业务流程。
例子完整的代码:https://github.***/gorylee/learnDemo/tree/master/camundaDemo