本篇将在上一篇应用的基础上开发一个最基本的 ”流程Demo“ !
提要 简单总结一下《Camunda工作流引擎一》 中关于表的知识,大体上分为 4 类:
act_RE_* :RE 表示 repository,这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。
*act_RU_\ **:RU 表示 runtime,这些运行时的表,包含流程实例、任务、变量、异步任务等运行中的数据。只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录,这样来尽量使运行时表可以一直很小速度很快。
act_HI_* :HI 表示 history,这些表包含历史数据,比如历史流程实例、遍历、任务等等。
act_GE_* :GE 表示 general,通用数据,用于不同场景下。
这些表存储了流程相关的各种信息,那么我们如何去对其进行CRUD呢?按照传统的思路,那自然是写 domain、dao、service 去进行对上述表的操作。
但是,实际上 Camunda 已经将上述表的操作给我们封装好了:
可以通过流程引擎去获取到各种 Service,从而进行一些流程操作:RepositoryService,资源管理类;RuntimeService,流程运行管理类;TaskService,任务管理类;HistoryService,历史管理类;ManagerService,引擎管理类等等。。。(在 Spring 项目中更多的是将这些 Service 也交由 Spring 容器进行管理控制)
引 本篇将按照下述的开发第一个 Camunda 流程Demo 的基本步骤来进行:
整合 Camunda(添加依赖)。
完善必要的配置。
实现业务流程建模(绘制 BPMN )。
工具下载地址: https://camunda.com/download/modeler/
部署流程定义。
启动流程实例。
查询代办任务。
处理代办任务。
结束流程。
1、2两个步骤,在上一篇中已经完成了,这两部也是启动应用、创建相关表的先行条件。以公司请假 审批流程为例,直接从第 3 步开始!由简到繁!
绘制业务流程 先看一下绘制好的样子:
首先创建一个开始事件 )和一个结束事件 ,可以修改两个事件的 id 和 name,或者不作任何修改。
接下来就是创建 Task ,然后点击右侧的”小扳手”设置为用户任务,可以根据需要修改用户任务的 Id、Name 和 Version Tag,或者不做修改使用默认,重点是 assignee 这个就是任务的处理人,可以有三种方式进行设置:
直接字符串写死 。
UEL-value ,是 Java EE6 规范的一部分,写法:${变量名} 或者 ${对象.属性名},本篇也将采用这种形式进行设置,以”员工输入”任务(input)为例:
如何传这个变量呢?后面运行流程实例的时候会再次提到!
UEL-Method ,写法:${bean.getId()}、${xxxService.findIdByName(empName)},bean 是 Spring 容器中的一个 bean,调用这个 bean 的 getId() 方法,xxxService 也是 Spring 容器中的一个 bean,其中 empName 是流程变量,empName 作为参数传到 findIdByName() 方法中。
用同样的方法创建另外两个用户任务 :approve1(部门审批) 和 approve2(人事审批)。
最后使用最后将事件和任务按照顺序连接起来(顺序流)。
将文件另存为 holiday.bpmn。
PS:bpmn 文件本质上是 xml 配置文件,只是我们的工具将其解析为了这样的图形样式,去方便使用者去进行业务流程模型的创建。
部署流程定义 定义好了 bpmn 文件,就可以将其进行部署了!首先在《Camunda工作流引擎一》 中那个 SpringBoot 项目的资源路径下建一个 BPMN 文件夹,将我们的 bpmn 文件 copy 进去:
然后就是使用我们开头说的各种工作流已经帮我们定义好的 Service 去进行流程部署操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Autowired RepositoryService repositoryService; @Test void deploy () { Deployment deploy = repositoryService.createDeployment() .addClasspathResource("BPMN/holiday.bpmn" ) .deploy(); System.out.println(deploy.getName()); }
除了这种最简单的部署方式之外,还可以同时部署 .bpmn 文件对应的 .png 图片、压缩包的方式进行部署等等。。。可以根据业务的需求选择合适的方式。
部署完成后可以去观察数据库表的变化:act_re_deployment
中会新增本次的部署信息、act_re_procdef
流程定义的一些信息、act_ge_bytearray
部署的资源文件。。
PS:流程定义和流程实例,很像是 Java 中类定义和 对象(实例) 的关系。
启动流程实例 有了流程定义,我们就可以去启动对应的流程实例!同时会演示“绘制业务流程”时留下的问题:如何给 assignee 中的 UEL-value 表达式进行传参!
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void startProcessInstanceWithAssignee () { Map<String,Object> map = new HashMap<>(); map.put("employee" ,"zhangsan" ); map.put("deptment" ,"lisi" ); map.put("personal" ,"wangwu" ); ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday" , map); System.out.println(holiday.getProcessInstanceId()); }
一个流程实例启动后,可以去观察 act_hi_actinst
表中记录了每个任务的相关信息(审批人、开始时间、结束时间等等)、act_hi_identitylink
表中记录了流程实例的参与者、act_hi_procinst
表中是流程实例的信息、act_hi_tastinst
记录了流程实例中每个任务实例的信息、act_ru_identitylink
表中记录了运行时的流程实例当前的参与者、act_ru_tast
表记录了当前运行中的任务、act_ru_variable
表中记录了设置的参数、等等。。。
流程变量 在业务流程推进的过程中,可能会涉及到很多流程变量。
Camunda 支持的变量类型不限于:String、int、short、long、double、date、binary、serializable(Java 对象需要去实现序列化接口,并生成 serialVersionUID) 等等。。
流程变量的作用域默认是一个流程实例(从流程启动到流程结束),一个流程实例是一个流程变量最大范围的作用域,所以被称为 global 变量;作用域也可以是一个任务(Task)或者一个执行实例(execution),所以被称为 local 变量。
PS:global 变量中变量名不允许重复,设置相同的变量名,后设置的值会覆盖先设置的值。local 变量和 global 变量可以变量名相同,没有影响。
设置流程变量的方法也有多种:
启动流程时设置变量:
1 2 3 4 5 6 Map<String,Object> params = new HashMap<>(); params.put("xxx" ,"xxx" ); params.put("xxx" ,"xxx" ); ... ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday" ,params);
完成任务时设置变量:
1 2 taskService.complete(task.getId(),params);
设置global/local变量:
1 2 3 4 5 6 7 8 9 10 Map params = new HashMap(); params.put("days" ,3 ); params.put("type" ,"休假" ); ... runtimeService.setVariable("excutionId" ,"key" ,"value" ); runtimeService.setVariables("excutionId" ,params); runtimeService.setVariableLocal("excutionId" ,"key" ,"value" ); runtimeService.setVariablesLocal("excutionId" ,params);
任务监听器 除此之外 Camunda 还可以给每个任务节点设置监听器 :
就以 ”用户输入“ 这个用户任务为例,为其设置一个任务监听器:
既然设置了 Java Class 类型的监听器,那么就需要实现一个监听器:
1 2 3 4 5 6 public class UserListener implements TaskListener { @Override public void notify (DelegateTask delegateTask) { System.out.println("给用户发一个消息!" ); } }
监听器需要去实现 TaskListener 接口,并重写其 notify()
方法,之后当这个任务节点被创建的时候,就会去执行这个任务监听器中的 notify()
方法。
PS:执行监听器使用方法也类似,需要实现 org.camunda.bpm.engine.delegate.ExecutionListener 接口。
一个示例
依然是请假流程,除了开始事件和结束事件,还有 input(用户任务—员工输入)、approve1(用户任务—部门审批)、approve2(用户任务—人事审批),以及一个系统任务—数据同步方法(模拟保存请假信息)。
给两个审批人都添加了监听器,当其任务创建时,会调用监听器的 notify()
方法给予 assignee 一个模拟的通知:
1 2 3 4 5 6 public class ApproverNotice implements TaskListener { @Override public void notify (DelegateTask delegateTask) { System.out.println("给" + delegateTask.getAssignee() +"发一个消息!" ); } }
如果员工请假天数少于等于 3 天,则只需要部门审批(approve1)之后即可审批结束保存数据,如果请假天数大于 3 天,则还需要人事审批(approve2)之后才能完成审批保存数据:
上图是其中的一条连线(序列流)。
当审批结束,则进入到系统任务(autoSync)保存请假信息:
1 2 3 4 5 6 public class AutoSync implements JavaDelegate { @Override public void execute (DelegateExecution delegateExecution) throws Exception { System.out.println("存储请假信息!!!!" ); } }
这只是一个及其简化的示例,可以在每个用户任务后面添加排他网关,审批通过则继续,驳回时则直接到结束事件,只有当用户任务都审批通过才到 autoSync 任务。
部署上述流程后,就可以开始启动一条流程,观察表中的变化:
1 2 3 4 5 6 7 8 9 10 public void runProcinst () { Map<String,Object> params = new HashMap<>(); params.put("employee" ,"zhangsan" ); params.put("leave" ,new Leave("NO00001" ,"休假" ,new Date())); params.put("days" ,2 ); ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday" ,params); System.out.println(holiday.getProcessDefinitionId()); System.out.println(holiday.getId()); System.out.println(holiday.getProcessInstanceId()); }
启动流程后,可以在 act_hi_procinst 表中看到开启的流程实例, act_ru_task 和 act_hi_taskinst 表中看到正在运行中的用户任务实例,act_ru_variable、act_ru_varinst 和 act_hi_detail 表中找到设置的参数(其中 Leave 对象还有其对应的 bytearray_id_ 可以在 act_ge_bytearray 中找到其更具体的参数内容)
完成当前任务,向前推进:
1 2 3 4 5 6 7 8 9 10 public void taskComplete () { Task task = taskService.createTaskQuery() .taskAssignee("zhangsan" ) .singleResult(); Map<String,Object> params = new HashMap<>(); params.put("deptment" ,"lisi" ); taskService.complete(task.getId(),params); }
执行方法,控制台上打印 给lisi发一个消息!
说明监听器在部门审批(approve1)用户任务创建时被调用了。
继续观察启动流程时介绍的表,act_ru_task 中之前 zhangsan 的用户任务已经被删除,在 act_hi_taskinst 中已经变为 completed 状态;但是由于我们设置的是 global 变量,在 act_ru_variable 中还可以找到开启流程时设置的参数;其他的表中也会有记录,细细观察会有更多收获,碍于篇幅,就不再赘述。
继续执行推进 lisi 的当前任务:
1 2 3 4 5 6 7 8 9 public void taskComplete () { Task task = taskService.createTaskQuery() .taskAssignee("lisi" ) .singleResult(); Map<String,Object> params = new HashMap<>(); params.put("personal" ,"wangwu" ); taskService.complete(task.getId(),params); }
控制台打印 存储请假信息!!!!
,并没有去执行人事审批(approve2)用户任务,而是直接去执行数据同步(autoSync)系统任务,说明连线(顺序流)设置的条件表达式起效了(开启流程时,设置的请假天数是 2)。
此时, act_ru_task 表中的已经没有当前流程实例的任务了,流程结束。act_hi_taskinsk 表中此流程实例的用户任务也都变成了 completed 状态。
总结 初学,仅作为学习记录,备忘。
本篇仅作为从0到1的入门过程,不过是 Camunda 的冰山一角。
下一篇继续学习记录 Camunda 的其他组件功能!
文末放出笔者在学习本篇内容时的测试手稿:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 @SpringBootTest class CamundaDemoApplicationTests { @Autowired RuntimeService runtimeService; @Autowired TaskService taskService; @Autowired HistoryService historyService; @Autowired RepositoryService repositoryService; @SneakyThrows @Test void deploy () { Deployment deploy = repositoryService.createDeployment() .addClasspathResource("bpmn/holiday.bpmn" ) .deploy(); System.out.println(deploy.getId()); } @Test public void runProcinst () { Map<String,Object> params = new HashMap<>(); params.put("employee" ,"zhangsan" ); params.put("leave" ,new Leave("NO00001" ,"休假" ,new Date())); params.put("days" ,2 ); ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday" ,params); System.out.println(holiday.getProcessDefinitionId()); System.out.println(holiday.getId()); System.out.println(holiday.getProcessInstanceId()); } @Test public void taskQuery () { List<Task> tasks = taskService.createTaskQuery() .processDefinitionKey("holiday" ) .list(); for (Task task : tasks) { System.out.println(task.getAssignee()); System.out.println(task.getId()); System.out.println(task.getName()); System.out.println(task.getTenantId()); } } @Test public void taskComplete () { Task task = taskService.createTaskQuery() .taskAssignee("lisi" ) .singleResult(); Map<String,Object> params = new HashMap<>(); params.put("personal" ,"wangwu" ); taskService.complete(task.getId(),params); } @Test public void queryDefine () { ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); List<ProcessDefinition> definitions = query.processDefinitionKey("holiday" ) .orderByProcessDefinitionVersion() .desc() .list(); for (ProcessDefinition definition : definitions) { System.out.println(definition.getDeploymentId()); System.out.println(definition.getName()); System.out.println(definition.getVersion()); System.out.println(definition.getId()); System.out.println(definition.getKey()); } } @Test public void deleteDefine () { ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); List<ProcessDefinition> definitions = processDefinitionQuery.processDefinitionKey("holiday" ) .orderByProcessDefinitionVersion() .asc() .list(); ProcessDefinition processDefinition = definitions.get(0 ); if (processDefinition != null ){ repositoryService.deleteDeployment(processDefinition.getDeploymentId(),true ); } } @SneakyThrows @Test public void outputBPMNFile () { List<ProcessDefinition> res = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("holiday" ) .list(); ProcessDefinition holiday = res.get(0 ); InputStream BPMNIs = repositoryService.getResourceAsStream(holiday.getDeploymentId(), holiday.getResourceName()); FileOutputStream outputStream = new FileOutputStream("E:\\流程\\" + holiday.getResourceName(),true ); IOUtils.copy(BPMNIs,outputStream); } @Test public void queryHistory () { List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery() .finished() .orderByHistoricActivityInstanceEndTime() .asc() .list(); for (HistoricActivityInstance instance : list) { System.out.println(instance.getActivityId()); System.out.println(instance.getProcessDefinitionKey()); System.out.println(instance.getAssignee()); System.out.println(instance.getStartTime()); System.out.println(instance.getEndTime()); System.out.println("=============================" ); } } @Test public void startProcInstAddBusinessKey () { ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday" , "0001" ); System.out.println(holiday.getBusinessKey()); } @Test public void activateOrSuspendProcInsts () { List<ProcessDefinition> holidays = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("holiday" ) .list(); ProcessDefinition holiday = holidays.get(0 ); boolean suspended = holiday.isSuspended(); if (suspended) { repositoryService.activateProcessDefinitionById(holiday.getId(),true ,null ); System.out.println("定义" +holiday.getId()+"激活" ); }else { repositoryService.suspendProcessDefinitionById(holiday.getId(),true ,null ); System.out.println("定义" +holiday.getId()+"挂起" ); } } @Test public void activateOrSuspendProcInst () { ProcessInstance holiday = runtimeService.createProcessInstanceQuery() .processInstanceBusinessKey("0001" ) .singleResult(); boolean suspended = holiday.isSuspended(); if (suspended) { runtimeService.activateProcessInstanceById(holiday.getId()); System.out.println("实例" +holiday.getId()+"激活" ); }else { runtimeService.suspendProcessInstanceById(holiday.getId()); System.out.println("实例" +holiday.getId()+"挂起" ); } } @Test public void startProcessInstanceWithAssignee () { Map<String,Object> map = new HashMap<>(); map.put("employee" ,"zhangsan" ); map.put("deptment" ,"lisi" ); map.put("personal" ,"wangwu" ); ProcessInstance holiday = runtimeService.startProcessInstanceByKey("holiday" , map); System.out.println(holiday.getProcessInstanceId()); } @Test public void setVariables () { Map params = new HashMap(); params.put("days" ,3 ); params.put("type" ,"休假" ); runtimeService.setVariable("excutionId" ,"key" ,"value" ); runtimeService.setVariables("excutionId" ,params); runtimeService.setVariableLocal("excutionId" ,"key" ,"value" ); runtimeService.setVariablesLocal("excutionId" ,params); } }
菜鸟本菜,不吝赐教,感激不尽!
更多题解源码和学习笔记:github 、CSDN 、M1ng