前言:因为公司有个项目需求要使用到工作流引擎,考查了市面各种的工作流引擎,对比它们之间的优劣势,最后选择Camunda工作流引擎。此前自己对Camunda工作流引擎了解的并不多,所以就记录下自己学习Camunda工作流引擎到springboot项目中整合Camunda工作流引擎使用的过程。
在上一篇文章中已经介绍了Camunda Platform和Modeler创建工作流的玩法了。在本文就将Camunda 应用在生产项目的各种审核流程中,重点是springboot整合Camunda ,并使用Camunda 的各种API。因为camunda本来就是一个轻量级的框架,项目中就主要使用Camunda 的工作流的流程流转和审核,流程的创建和前端bpmn页面流程编辑器部分这里就不介绍使用了。因为在经历几个生产项目,无论是使用flowable或者Camunda 工作流框架,流程bpmn文件都是提前通过其他工具创建好,然后放在项目中加载部署的,项目主要是对应的流程的流转和审核。不多废话,下面就直接进入正题。
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-compatibility/
org.camunda.bpm.springboot camunda-bpm-spring-boot-starter 7.16.0
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的相关的bean没办法自动注入到spring容器中,会无法正常启动项目。camunda的相关脚本语句,去官网找到对应版本(此例中Camunda版本为7.16.0)的sql脚本文件:
https://camunda.com/download/
解压后,找到./configuration/sql目录下即可找到sql文件,engine和identity都执行。项目中使用的是mysql数据,所以执行mysql的脚本。
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模型。
下面是项目中排查问题用的比较多,比较重要的几张表的说明
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
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 |
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
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 | 删除时间 |
流程定义表,包含所有已部署的流程定义,诸如版本详细信息、资源名称或挂起状态等信息。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
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) | 是否是可启动流程 |
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 |
运行时流程人员表,主要存储当前节点参与者的信息
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
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 |
流程运行时任务表,包含所有正在运行的流程实例的所有打开的任务,包括诸如相应的流程实例、执行以及元数据(如创建时间、办理人或到期时间)等信息。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
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 |
历史流程审批意见表,存放历史流程的审批意见。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
ID_ | varchar(64) | 主键 | |
TYPE_ | varchar(255) | NULL | 类型(event事件、comment意见) |
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 | 移除时间 |
历史的流程运行变量详情记录表。流程中产生的变量详细,包括控制流程流转的变量,业务表单中填写的流程需要用到的变量等。
字段名称 | 字段类型 | 可否为空 | 描述 |
---|---|---|---|
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是大多数是相同的,所以在项目中封装一个工具类提供使用。
/** * camunda 流程工具类 */ @Component @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, Mapmap){ 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 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 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 completeTask(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.complete(taskId); } /** * 领取并完成任务 * @param taskId 任务id * @param assignee 用户名 */ public static void claimAndCompleteTask(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.complete(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.complete(taskId); } /** * 查询待办任务,参数:候选用户、业务id * @param candidateUser 候选用户 * @param key 流程标识,需唯一,如果存在相同key则会启动版本号最新的流程 * @param businessKey 业务id * @return */ public static List 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 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 claimAndCompleteTask(String key, String businessKey,String assignee){ //查询待办任务 List tasks = FlowUtil.getTaskByCandidateUserAndBusinessKey(key, businessKey, assignee); if(CollUtil.isEmpty(tasks)){ log.info("{}完成审批任务失败:{}--{}",assignee,key,businessKey); throw new ResultException("暂未到您审批"); } for (Task task : tasks) { FlowUtil.claimAndCompleteTask(task.getId(),assignee); } if(CollUtil.isEmpty(FlowUtil.getTasksByBusinessKey(key,businessKey))){ return true; }else{ return false; } } /** * 查询历史任务 * @param key * @param businessKey * @return */ public static List 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 getHistoricIdentityLinkLog(String key, String taskId){ return util.historyService.createHistoricIdentityLinkLogQuery() .processDefinitionKey(key) .taskId(taskId) .list(); } /** * 查询任务 * @param taskId * @return */ public static List getTaskIdentityLink(String taskId){ return util.taskService.getIdentityLinksForTask(taskId); } }
下面是费用审核的流程图
创建流程可以使用Camunda Modeler,具体的使用方法在我上一篇文章中有写到,又不了解的可以移步到:
https://blog.csdn.net/qq798867485/article/details/131439688
下面是流程图的创建和每个节点的设置情况
一定要放在这个目录下,因为之前的yml的配置指定了自动部署的目录,不然无法自动部署。除非你在yml配置中不制动自动部署的目录。
FlowUtil.startProcess(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId(),params);
这个方法就是 创建费用审核流程
@Override @Transactional(rollbackFor = Exception.class) public void submit(Expense expense) { dao.insert(expense); ListuserList = userService.listAll(new UserQueryBo()); Set companyUsers = userList.stream().filter(x -> x.getUserRole() == 1) .map(item -> item.getId().toString()).collect(Collectors.toSet()); Set groupUsers = userList.stream().filter(x -> x.getUserRole() == 2) .map(item -> item.getId().toString()).collect(Collectors.toSet()); Set headUsers = userList.stream().filter(x -> x.getUserRole() == 3) .map(item -> item.getId().toString()).collect(Collectors.toSet()); Map params = new HashMap<>(); params.put("companyUsers", companyUsers); params.put("groupUsers",groupUsers); params.put("headUsers",headUsers); FlowUtil.startProcess(BizFlowTypeEnum.EXPENSE_APPROVE.getKey(),expense.getId(),params); }
##查看这个审核节点是否为审核流程最后的一个节点
FlowUtil.claimAndCompleteTask(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.claimAndCompleteTask(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.com/gorylee/learnDemo/tree/master/camundaDemo