CamundaDemo

本Demo采用springBoot和camunda内嵌的方式。 在SpringBoot中加入camunda的依赖后在springBoot启动类上加上@EnableProcessApplication注解即可完成配置,  配置完毕后可以注入camunda引擎的RunTimeService、TaskService等工具类操作camunda工作流引擎。

demo源码: https://github.com/Himly1/camunda-demo

Note: 如果需要调用demo中的api可以在启动demo后访问localhost:8080/swagger-ui.html来方便快捷的完成调用.

如下图所示, 应用和引擎作为一个整体使用同一个db。

若后期存在service和web分离, 或者采用集群也很容易地完成拓展部署.

假设web和service分离, web和service均需要与camunda引擎交互。

那么最少会存在2个camonda引擎, 每个引擎都与同一个db做交互,这样一来只需要保证所有引擎访问的db一致即可。

假设后期流量非常大,配置了2个web集群, 那么会存在2个camunda引擎, 而这两个引擎都与同一个db做交互,目前来看不会存在问题.

demo流程图定义如下(对应源码中resource/project.dpmn文件)

学生报名:

startEvent名称为学生报名, id默认生成

其中有个表单,表单中有一个参数recordId。 recordId指向的是学生报名成功后存储在db报名信息表中的一条数据主键.

学生通过restApi发起项目报名申请, 处理完业务逻辑后如用户报名成功需要创建一个当前流程定义的实例。 如下代码所示

@ApiOperation(value = "项目报名")@PostMapping(value = "/{projectId}/users{userId}")public boolean ParticipatingProject(@PathVariable Long projectId, @PathVariable Long userId) {//ignore argument verify//save the record to dbLong savedRecordId = 3L;//start a new instance of the processMap<String, Object> variables = new HashMap<String, Object>();variables.put(ProjectProcessConstant.VAR_NAME_SCHOOL, "上海交通大学");variables.put(ProjectProcessConstant.VAR_NAME_STUDENT, String.valueOf(userId));variables.put(ProjectProcessConstant.FORM_RECORD_ID, savedRecordId);ProcessInstance instance = runtimeService.startProcessInstanceByKey(ProjectProcessConstant.PROCESS_ID, variables);if (instance == null) {return false;}else {return true;}}

runtimeService是注入进来的, 通过startProcessInstaceByKey方法来完成开启一个流程定义的实例。 方法的第二个参数是流程定义中的变量值。  比如VAR_NAME_SCHOOL表明的是将“上海交通大学”赋值给流程定义中的school变量(如下图)

流程开启后我们可以通过访问项目首页,在本demo中就是localhost:8080输入camunda的用户名密码(本demo中账号密码均为demo)来查看具体情况, 如下图

进入实例中我们能够看到当前实例执行情况,如下图

UserTask是需要人员来执行的, 那么在此demo中就需要指定学校人员来先获取需要审批的项目报名申请, 其后就申请作出审批。

@ApiOperation(value = "获取需要审批的项目申请列表")@GetMapping(value = "/project/approve/list")public @ResponseBody List<ProjectParticipateRequestRecord> getAllProjectParticipateRequest(String schoolName, Integer reviewLevel) {LOGGER.info("The school name is {}", schoolName);//get the taskListList<Task> tasks;if (reviewLevel.equals(1)) {tasks = taskService.createTaskQuery().taskName(ProjectProcessConstant.TASK_NAME_FIRST_LEVEL_REVIEW).taskCandidateGroup(schoolName).list();}else {tasks = taskService.createTaskQuery().taskName(ProjectProcessConstant.TASK_NAME_SECOND_LEVEL_REVIEW).taskCandidateGroup(schoolName).list();}

以上代码中通过taskService来查询task. 首先通过taskName来查, 我们需要查询的是一级审核任务, 在流程定义中一级审核的name就是”一级审核”, 其次我们还需要通过学校名称作为UserTask的group值来过滤只获取当前学校需要审批的学生报名申请.

在流程定义中一级审核任务的group是一个变量${school}(请翻上面的图), 在学生报名成功后开启一个实例时,我们将“上海交通大学”赋值给了此变量。 那么如果上图代码中请求参数schoolName不是“上海交通大学”则无法查询到任何task, 因为目前只有上海交通大学才有一名学生提交了报名申请并开启了一个流程定义实例。

因为一个流程定义会有n个实例, 那么自然会有n条一级审批的user task.  为了审批时能够知道当前审批的是哪个实例的一级审批user task就需要在响应参数中返回taskId,具体作用下面再细说

如下图, 输入上海交通大学我们看到有一条待审批的报名申请

输入其他名称时, 没有任何待审批的项目申请

当作出审批时将审批结果和审批taskId作为参数发送到后端, 后端通过taskId获取对应实例的一级审批user task并提交给camunda引擎, 引擎根据审批结果及gateWay来决定走向。

@ApiOperation(value = "审批项目申请")@PutMapping(value = "/project/participateRequests/{taskId}")public boolean approveProjectParticipateRequest(@PathVariable String taskId, boolean needExtraInfo, boolean passed, String schoolName) {Task task = taskService.createTaskQuery().taskCandidateGroup(schoolName).taskId(taskId).singleResult();if (task == null) {LOGGER.error("The task not found, task id is {}", taskId);return false;}else {//business logic here//Into next stepLOGGER.info("The taskId is {}", taskId);Map<String, Object> variables = new HashMap<>();variables.put(ProjectProcessConstant.FORM_EXTRA_INFO_1,  needExtraInfo);variables.put(ProjectProcessConstant.FORM_APPROVED_1, passed);taskService.complete(task.getId(), variables);return true;}}

上述代码中首先通过获取当前用户就职的学校(相当于鉴权), 通过task的group(${school}变量)和taskId来获取指定的task.

之所以不直接使用taskId来调用taskService.complete()方法将结果提交给camunda引擎是因为当前用户只能对他们学校的报名申请作出审批, 若传递的taskId指向的task的group属性并非“上海交通大学”会存在严重的问题。

其中FORM_EXTRA_INFO_1和FORM_APPROVED_1常量对应的一级审核task的表单, 如下图

假设我们将extra_info_1设置为true, approved_info 任意, 我们可以在后台看到当前流程定义实例的执行情况

网关"是否需要额外材料"是根据extra_info_1字段来决定流程走向的, 上个步骤中我们将extra_info_1设置为了true, 那么上图中可以看到当前实例的流程走到了"上传额外材料"的user task.

接下来需要有个api来获取等待指定用户上传额外材料的记录列表,如下代码

@ApiOperation(value = "获取学生需要上传额外材料的记录")@GetMapping(value = "/users/{userId}/extraInfo/list")public List<UploadExtraInfoRecord> getUploadExtraTask(Long userId) {List<Task> uploadExtraInfoTask =taskService.createTaskQuery().taskAssignee(String.valueOf(userId)).taskName(ProjectProcessConstant.TASK_NAME_UPLOAD_EXTRA_INFO).list();List<UploadExtraInfoRecord> records = new ArrayList<>(uploadExtraInfoTask.size());uploadExtraInfoTask.forEach( task -> {UploadExtraInfoRecord record = new UploadExtraInfoRecord();record.setTaskId(task.getId());//the upload url of extra info is up to the variablerecord.setTheUploadUrlOfExtraInfo("www.google.com");records.add(record);});return records;}

首先同样在上传额外材料的user task中有个所属用户的属性, 属性使用的是变量如下图

在开启实例时我们将userId变量赋值给了${student}变量, 如下图

 @ApiOperation(value = "项目报名")@PostMapping(value = "/{projectId}/users{userId}")public boolean ParticipatingProject(@PathVariable Long projectId, @PathVariable Long userId) {//ignore argument verify//save the record to dbLong savedRecordId = 3L;//start a new instance of the processMap<String, Object> variables = new HashMap<String, Object>();variables.put(ProjectProcessConstant.VAR_NAME_SCHOOL, "上海交通大学");variables.put(ProjectProcessConstant.VAR_NAME_STUDENT, String.valueOf(userId));variables.put(ProjectProcessConstant.FORM_RECORD_ID, savedRecordId);ProcessInstance instance = runtimeService.startProcessInstanceByKey(ProjectProcessConstant.PROCESS_ID, variables);if (instance == null) {return false;}else {return true;}}

那么获取用户需要上传额外材料的记录可以通过任务所属用户+任务名来获取。

如下代码

List<Task> uploadExtraInfoTask =taskService.createTaskQuery().taskAssignee(String.valueOf(userId)).taskName(ProjectProcessConstant.TASK_NAME_UPLOAD_EXTRA_INFO).list();

调用获取指定用户待上传额外材料列表的api来查看如下所示, 可以看到有一条记录

同样的, 也需要一个用户上传额外材料的api, 如下图

@ApiOperation(value = "上传指定项目所需的额外资料")@PostMapping(value = "/{projectId}/users/{userId}/extraInfo")public boolean  uploadExtraInfo(@PathVariable Long projectId, @PathVariable Long userId,  String extraInfo, String taskId) {//must verify the task of the taskId pointing is belong the current user.Task task = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)).taskName(ProjectProcessConstant.TASK_NAME_UPLOAD_EXTRA_INFO).taskId(taskId).singleResult();if (task == null) {LOGGER.error("The task not found.");LOGGER.error("the assignee is {}, taskName is {}, taskId is {}.", userId, ProjectProcessConstant.TASK_NAME_UPLOAD_EXTRA_INFO, taskId);return false;}else {//upload extra info to db.//business logic here//into next steptaskService.complete(task.getId());return true;}}

同样通过assIgnee及taskName及taskId来拿到task对象,  并提交该task使得camunda引擎继续工作。

上传额外材料完毕后的流程如下:

又返回到了一级审核, 这次一级审核中我们将extra_info_1设置为false, approved_1设置为true, 如下图

可以看到流程已经走到了二级审核, 二级审核的group是教务处,并没有使用变量。

我们通过api来查询下教务处需要审批的报名申请, 如下图

二级审批表单如下

同样有两个参数, 意义与一级审核中的一致。 在一级审核中提交的表单数据只会被保留, 若在二级审核中没有更改则会继续向下传递, 若更改了数据则传递更改后的数据。

这次我们将extra_info_1设置为false, approved_1设置为true来看下流程的走向.

此时查看后台发现没有实例在运行, 这表明之前存在的流程定义实例已经执行完毕。

我们看到二级审批通过后需要执行一个serviceTask

下面看看serviceTask. 其中service的实现方式是Delegate expression, 值为${smsServiceTask}, 这表明的是由spring的smsServiceTask这个 Bean来执行当前的service

smsServiceTask如下

package org.camunda.bpm.getstarted.loanapproval.camunda.tasks.service;import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.util.Map;@Component
public class SmsServiceTask implements JavaDelegate {private static final Logger log = LoggerFactory.getLogger(SmsServiceTask.class);private final TaskService taskService;public SmsServiceTask(TaskService taskService) {this.taskService = taskService;}public void execute(DelegateExecution delegateExecution) throws Exception {Map<String, Object> variables = delegateExecution.getVariables();log.info("variables is {}", variables);String studentId = (String)variables.get("student");log.info("success send sms message to the student {}", studentId);}
}

需要实现JavaDelegate类并重写excute方法, 具体请查阅此官放doc https://docs.camunda.org/manual/7.7/user-guide/process-engine/delegation-code/

当之前流程实例实例的二级审批执行完毕且表单中值extra_info_1 = false, approved_1 = true时控制台打印如下

这表明执行到短信通知学生这个serviceTask时对应的实现被自动调用了, 调用成功后继续执行, 最后执行到了endEvent, 此时结束该实例表明流程执行完毕.

QA

1.应用部署后,怎么在不重启应用的情况下更新已经部署的流程定义?

简单来说,可以通过restApi来同正在工作中的引擎交互, 具体的api可到官网查询。

比如当前demo中流程定义的key为project, 那么在同样key的情况下我可以发布一个新的流程定义, camunda引擎会根据key的不同自动新增或更新流程定义。 当Java应用通过key启动一个更新后的流程定义实例时,创建的是更新后的流程定义实例。

具体文档如下:https://docs.camunda.org/manual/7.5/user-guide/process-engine/process-versioning/

Camunda 工作流引擎 demo相关推荐

  1. Camunda工作流引擎三

    本篇继续拓宽对 Camunda 工作流的学习! <Camunda 工作流引擎一> <Camunda 工作流引擎二> 提要 文章目录 提要 正文 组任务 网关 排他网关 并行网关 ...

  2. Camunda工作流引擎一

    实习工作中需要用到工作流引擎,去实现业务审批流的功能模块,由于 Flowable 不支持 MariaDB (重要原因之一),所以项目中选择了 Camunda 工作流引擎. 由于没有接触过工作流引擎,所 ...

  3. camunda工作流引擎流程定义部署 流程定义查询 激活流程实例

    camunda工作流引擎流程定义部署 流程定义查询 激活流程实例 1.通过xml字符串部署流程定义 /*** 通过xml字符串部署流程定义* @param processModelVo* @retur ...

  4. Camunda工作流引擎简记

    本文转载自玩转Camunda之实战篇-赶紧收藏起来吧_哔哩哔哩_bilibili 其中部分内容,经过本人修改 一.工作流相关介绍 BPM(BusinessProcessManagement),业务流程 ...

  5. Camunda工作流引擎之bpmn设计器定制

    在 Vue.js应用中,基于 bpmn-js. 定制 vue-bpmn-modeler 前段时间,由于公司业务需要工作流引擎,但是由于之前的工作流引擎是第三方付费的,又遇到客户需要本地地方部署,所以需 ...

  6. OA 系统工作流引擎 Camunda 实践(1)

    [审核人员看清楚再审核,我是把自己公司的案例分析一下,  这哪是广告???] Camunda是Activiti最大的贡献者之一(除Alfresco以外),同时也是它一个主 要的执行咨询合作伙伴.cam ...

  7. 三、Camunda工作流的表和用途说明(实践是检验真理的唯一标准)

    本人在工作中用的Camunda7.11版本共47张表. camunda工作流的表大体上分为 5 类: ACT_RE_*: 'RE'表示流程资源存储,这个前缀的表包含了流程定义和流程静态资源(图片,规则 ...

  8. Camunda工作流平台的使用

    工作流可以实现业务流程的自动化,用户可以自己定义工作流程,通过流程来把常用的任务组织起来,而无需在程序中固化流程.这也符合当今微服务,低代码开发的趋势. Camunda是目前主流的一个工作流平台,遵循 ...

  9. Camunda将工作流引擎引入到微服务领域

    今天,工作流程自动化的软件公司Camunda宣布,Zeebe的第一个生产就绪版本现在可以作为免费的社区版下载. Zeebe是一个为云架构而构建的现代工作流引擎,可提供对跨多个微服务的工作流的可见性和控 ...

最新文章

  1. 快速开发rails、==常用插件==
  2. 用机器学习还原《隐秘的角落》那些被修改的台词
  3. ISE中使用Notepad++的关联设置以及Notepad++的护眼设置(设置背景色)
  4. java 实现HTTP连接(HTTPClient)
  5. git合并分支的策略(赞)
  6. akka连接是什么_什么是Akka?
  7. UE3 渲染线程的分析及优化
  8. java子类代码块_java中父类子类静态代码块、构造代码块执行顺序
  9. redis系列(一):安装配置
  10. 【原】[webkit移动开发笔记]之空链接是使用javascript:void(0)还是使用#none
  11. 【科研论文】某雷达自动测试系统研制–基于全硬件TCP/IP协议栈芯片W5300
  12. python调试利器pysnooper
  13. 黑马程序员传智播客 进程、线程、协程对比
  14. 开源自动化运维工具_批量与重复运维压力如何破?了解一下这款自动化运维工具...
  15. 微信公众号怎么申请注册?看这一篇就够了
  16. 用python计算圆周率
  17. opencv------绘制文本
  18. 微信群发消息注意事项
  19. SEO优化方案及SEO操作流程-邹川
  20. 美国oracle球场,【Dubnation翻译】甲骨文球馆的恢弘“绝唱”

热门文章

  1. java毕业设计基于Vue框架的养生系统mybatis+源码+调试部署+系统+数据库+lw
  2. TypeError: unsupported format string passed to NoneType.__format__
  3. 思科向 IETF 提交 TrustSec 标准草案
  4. 安装/卸载docker
  5. 在企业运营管理中数据分析的重要作用和意义
  6. MongoDB-查询语句中$exists以及结合$ne、$nin、$nor、$not使用介绍
  7. Numpy学习——数组填充np.pad()函数的应用
  8. openlayers如何使用(三)动态数据加载
  9. 让陪伴机器人不再「直男」,读懂更多情绪 | 香港理工大学李嫣然
  10. Leetcode.1641 统计字典序元音字符串的数目