idea的Flowable BPMN visualizer插件
一 多人会签
- 使用场景
- 同一个任务Task,可能需要有3个都要去做审批。
- 这3个人可能是并行、串行地去做审批。
- 这3个人中,可能要3个人都通过才结束、2个人通过就可结束、甚至1个人通过也可结束。
- 案例演示:
- 第一步:ui,绘制流程图
- 第1步:空启动事件
- 第2步:用户任务
- 第a步:多人、并行与串行、结束规则,Multi instance(多实例):
- sequential:是否串行,true:是,false:否(并行)。
- loop cardinality:循环的次数。比如,有3个人就写3,表示此任务由3个人单循环完成。
- Collection:集合(候选人集合)。如可以填写persons(List<Pserson>),然后for循环persons。
- Element varialbe:元素实例。pseron,即上面for循环(List<Pserson> persons)的单个实例。
- Completion condition:完成的条件。如:填写EL表达式${multilnstanceCompleteTask.completeTask(execution)},此表达式的作用是从spring容器中获取相关的对象,并且调用相应的方法。
- 第b步:给task执行时,创建监听器,监听器通过行为触发。比如,在创建时触发:
- Event:监听的行为(事件)
- Type:
- java class:java类
- expression:表达式,如${}
- delegate expression:
- Expression:
- 第a步:多人、并行与串行、结束规则,Multi instance(多实例):
- 第3步:空结束事件
- 第二步:idea编程
- 第1步:定义多从会签的规则MulitiInstanceCompleteTask.java,控制多实例完成的规则:
@Component("mulitiInstanceCompleteTask")
public class MulitiInstanceCompleteTask implements Serializable {
/**
* 完成任务是需要触发的方法
* @param execution
* @return
* false 表示会签任务还没有结束
* true 表示会签任务结束了
*/
public boolean completeTask(DelegateExecution execution) {
System.out.println("总的会签任务数量:" + execution.getVariable("nrOfInstances")
+ "当前获取的会签任务数量:" + execution.getVariable("nrOfActiveInstances")
+ " - " + "已经完成的会签任务数量:" + execution.getVariable("nrOfCompletedInstances"));
//有一个人同意就通过
Boolean flag = (Boolean) execution.getVariable("flag");
System.out.println("当前意见:"+flag);
return flag;
}
}- flowable默认帮我们提供的流程实例的变量:
- nrOfInstances:总的会签任务数量。如上面设置的3。
- nrOfActiveInstances:当前获取的会签任务数量,即还没有完成的。
- nrOfCompletedInstances:已经完成的会签任务数量。
- flag:我们自定义的。
- return flag:控制当前会签是否结束的。
- flowable默认帮我们提供的流程实例的变量:
- 第2步:编写监听器(供上面使用)
@Component("mulitiInstanceTaskListener")
public class MulitiInstanceTaskListener implements Serializable {
public void completeListener(DelegateExecution execution){
System.out.println("任务:"+execution.getId());
System.out.println("persons:" + execution.getVariable("persons"));
System.out.println("person" + execution.getVariable("person"));
}
}- 作用:监听在任务Task的创建,在监听器里可以获取流程实例以及任务Task的相关信息。
- 第3步:部署流程实例
- 第4步:创建(启动)流程实例: @Test
void startFlow() throws Exception{
Map<String,Object> map = new HashMap<>();
// 设置多人会签的数据
map.put("persons", Arrays.asList("张三","李四","王五","赵六"));
ProcessInstance processInstance = runtimeService
.startProcessInstanceById("myProcess:1:ba1518fc-b22d-11ec-9313-c03c59ad2248",map);
}- 注意:这里会签人数我们设置为3,但实际给的人数是4个人,这里会不会有问题呢?不会。多人会签的规则,会签人数大于或等于配置人数即可。
- 第5步:验证1
- 效果:MulitiInstanceTaskListener.completeListener()被执行3次。因为会签从数设置为3,所以循环创建任务Task为3次,所以监听器走了3次。每次创建任务,就从person列表中获取一个候选人绑定给了任务task。如下,控制台打印:
- act_ru_task:有3条任务被创建
注意:但任务的分配人ASSIGNEE_没有值?这是因为我们没有去绑定它,需要我们去做动态绑定。比如,我们可以在监听器MulitiInstanceTaskListener.completeListener()进行绑定,即任务Task创建的时候进行动态绑定(指派)处理人。
- 第6步:通过任务id,去完成任务(查看多人会签任务时,有1个人去完成任务的效果是怎么样的)
@Test void completeTask1(){ Map<String,Object> map = new HashMap<>(); map.put("flag",false); //设置为true,结束多人会签 taskService.complete("71337501-b22e-11ec-a534-c03c59ad2248",map); System.out.println("complete ...."); }
- map.put("flag",false);
- 去完成任务task时,绑定一个流程变量flag=false。
- 其中taskService.complete("71337501-b22e-11ec-a534-c03c59ad2248",map);:
- 71337501-b22e-11ec-a534-c03c59ad2248:为任务Task的id(act_ru_task表中查找)。
- 因为ASSIGNEE_没有值,表明没有指派此任务的具体候选人,所以这里使用的是包含任务id参数的处理方法,去完成(处理)任务。而不是使用包含处理人参数的处理方法。
- map.put("flag",false);
- 第7步:验证2
-
MulitiInstanceCompleteTask.completeTask()被执行1次,控制台有如下打印。:
-
当前意见:false,表示当前会签任务没有结束,继续会签直到遇到true为止。
-
-
act_ru_task:还有2条任务(删除一条任务了)
-
-
第8步:再次通过任务id,去完成任务(查看多人会签任务时,有2个人去完成任务的效果是怎么样的)
-
map.put("flag",true); //设置为true,结束多人会签
-
-
第9步:验证3
-
MulitiInstanceCompleteTask.completeTask()又被执行1次,控制台有如下打印。:
当前意见:true,表示当前会签结束了,即“用户任务”这个节点完成了,流转空结束事件。
-
act_ru_task:3条任务都删除了,甚至连没有去完成的那1条任务也被删除了。
-
- 第1步:定义多从会签的规则MulitiInstanceCompleteTask.java,控制多实例完成的规则:
- 第一步:ui,绘制流程图
二 回退、驳回、撤消
- 概念
- 回退和驳回区别不大,其实可以把它们看成是同一个概念。
- 撤消与回退、驳回是有一点区别的。
- 撤消举例:流程流转到用户2之后,想撤回到用户1。这时用户2的历史任务就没有了。
- 串行的回退
-
应用场景
-
用户任务4回退到用户任务3,或者从用户任务4回退到用户任务1。
-
- 案例演示
- 第一步:ui,绘制流程图
- 用户任务1,user1
- 用户任务2,user2
- 用户任务3,user3
- 用户任务4,user4
- 第二步:idea编程
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 第3步:user1完成用户任务1,流转到用户任务2(user2),act_ru_task验证
- 第4步:user2完成用户任务2,流转到用户任务3(user3)
- 第5步:user3完成用户任务3,流转到用户任务4(user4)
- 第6步:从用户任务4(user4)回退到用户任务3(user3)
/** * moveActivityIdTo(),可以从当前的流程跳转到任意的节点 */ @Test public void rollbackTask(){ ProcessEngine processEngine = configuration.buildProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); runtimeService.createChangeActivityStateBuilder() .processInstanceId("67501") .moveActivityIdTo("usertask4","usertask3")//方法1:可以从当前活动的节点(Id),跳转指定的新的节点(Id) //.moveActivityIdsToSingleActivityId(currentActivityIds,newActivityId) .changeState();//改变状态 }
- 第7步:验证
- act_hi_actinst:操作后我们可以在对应的历史表中看到相关的信息:
- 第8步:user3完成用户任务3,流转到用户任务4(user4)
- 第9步:从用户任务4(user4)回退到用户任务1(user1)
@Test public void rollbackTask1(){ ProcessEngine processEngine = configuration.buildProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); List<String> newActivityIds = new ArrayList<String>(); newActivityIds.add("usertask1"); runtimeService.createChangeActivityStateBuilder() .processInstanceId("67501") //方法1:可以从当前活动的节点(Id),跳转指定的新的节点(Id) //.moveActivityIdTo("usertask4","usertask3") //方法2:从当前活动的节点(id),跳转到List<String> ids集合(多个节点)。 .moveSingleActivityIdToActivityIds("usertask4",newActivityIds) .changeState();//改变状态 }
- 第10步:验证
- act_hi_actinst:操作后我们可以在对应的历史表中看到相关的信息:
- 第11步:用户任务1 》用户任务2 》用户任务3 》用户任务4
- 第12步:从用户任务4(user4)回退到用户任务3(user3)
@Test public void rollbackTask1(){ ProcessEngine processEngine = configuration.buildProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); List<String> newActivityIds = new ArrayList<String>(); newActivityIds.add("usertask1"); runtimeService.createChangeActivityStateBuilder() .processInstanceId("67501") //方法1:可以从当前活动的节点(Id),跳转指定的新的节点(Id) //.moveActivityIdTo("usertask4","usertask3") //方法2:从当前活动的节点(id),跳转到List<String> ids集合(多个节点)。 //.moveSingleActivityIdToActivityIds("usertask4",newActivityIds) //方法3:不管当前节点是哪个了,就是要从当前执行实例跳到哪个节点。参数1:执行id,参数2:目标id。 .moveExecutionToActivityId("77501","usertask3") .changeState();//改变状态 }
- 第一步:ui,绘制流程图
- 注意事项
- 如果上面的流程图中,包含了一个排他网关,那也算是串行的。
- 如果上面的流程图中,包含了一个多用户实例,那也算是串行的。
- 可以看到任务又回到了user1处。也就是在串行的流程中,我们可以回退到任意的用户节点,当然这个串行也包括多人会签和排他网关节点。当然在回退的时候我们还可以使用
moveActivityIdTo(String currentActivityId,String newActivityId)
这个方法来处理。
-
- 并行的回退
-
应用场景
-
业务副总肯定是不能回退到行政副总。即使你可以强行跳过来,但这种逻辑是不合理的。
-
从业务副总回退到业务负责人,这是串行的,和上面的操作一样。
-
关注1:从并行节点回退到串行节点。并行审批流转到业务副总(没处理),同时流转到行政副总(没处理)。编码要实现,业务副总(没处理)回退到用户审批01(起始节点)。自动地,行政副总要自动回退到用户审批01(起始节点)。
-
关注2:并行审批流转到业务副总(没处理),同时流转到行政副总(已处理,到达并行网关之集合)。因为是并行网关,所以也要等待业务副总(没处理)审批是吧。这时想编码要实现,业务副总(没处理)回退到用户审批01(起始节点)。自动地,行政副总要自动回退到用户审批01(起始节点)。
-
关注3:从串行节点回退到并行节点。总经理回退到并行网关中的业务副总、行政副总。
-
关注4:总经理回退到用户审批01(起始节点),其实也是串行的,和上面的操作一样。
-
- 案例演示
- 第一步:ui,绘制流程图
- 并行网关
- 用户审批01,user1
- 业务负责人,user2
- 业务副总,user3
- 行政副总,user4
- 总经理,user5
- 第二步:演示1:业务副总(没处理)回退到用户审批01。行政副总会自动回退到用户审批01。
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 第3步:用户审批01,user1,完成任务
- task表:产生2条并行的任务task,业务负责人user2和行政副总user4。
- 第4步:业务负责人,user2,完成任务
- task表:流转到:行政副总user4、业务副总user3
- 第5步:业务副总(没处理)回退到用户审批01,重新去录入相关数据。行政副总会自动回退到用户审批01。
/** * 回退操作 * 业务副总驳回到到用户审批处 那么行政审批的应该自动回到到用户审批处 */ @Test public void rollbackTask(){ ProcessEngine processEngine = configuration.buildProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); // 当前的Task对应的用户任务的Id List<String> currentActivityIds = new ArrayList<String>(); currentActivityIds.add("usertask4"); // 业务副总 currentActivityIds.add("usertask3"); // 行政副总 // 需要回退的目标节点的用户任务Id String newActivityId = "usertask1"; // 用户审批01 // 回退操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId("95001") .moveActivityIdsToSingleActivityId(currentActivityIds,newActivityId) .changeState(); }
-
第6步:验证
-
task表
-
act_hi_actinst表,查看任务列表
-
-
第三步:演示2:并行审批流转到业务副总(没处理),同时流转到行政副总(已处理,到达并行网关之集合)。因为是并行网关,所以也要等待业务副总(没处理)审批是吧。这时想编码要实现,业务副总(没处理)回退到用户审批01(起始节点)。自动地,行政副总要自动回退到用户审批01(起始节点)。
-
第1步:完成任务:用户审批01,user1。业务负责人,user2。行政副总,user4。
-
第2步:验证
-
task表,只有1条任务,流转到(即等待)业务副总user3
-
act_hi_actinst表,查看任务列表
-
-
第3步:业务副总(没处理)回退到用户审批01,重新去录入相关数据。行政副总会自动回退到用户审批01:
/** * 回退操作 * 业务副总驳回到到用户审批处 */ @Test public void rollbackTask(){ ProcessEngine processEngine = configuration.buildProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); // 当前的Task对应的用户任务的Id List<String> currentActivityIds = new ArrayList<String>(); currentActivityIds.add("usertask4"); // 业务副总 //currentActivityIds.add("usertask3"); //行政副总 // 需要回退的目标节点的用户任务Id String newActivityId = "usertask1"; // 用户审批01 // 回退操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId("95001") .moveActivityIdsToSingleActivityId(currentActivityIds,newActivityId) .changeState(); }
-
-
第四步:演示3:总经理回退到并行网关中的业务副总、行政副总。
- 第1步:完成任务:用户审批01,user1。业务负责人,user2。业务副总,user3。行政副总,user4。
- 第2步:验证
- task表:只剩下总经理,user5
- 第3步:总经理user5,回退到并行网关里面(即业务副总,user3。行政副总,user4。)
@Test public void rollbackTask(){ ProcessEngine processEngine = configuration.buildProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); // 当前的Task对应的用户任务的Id List<String> newActivityId = new ArrayList<String>(); newActivityId.add("usertask2"); //业务负责人 newActivityId.add("usertask3"); //行政副总 // 回退操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId("95001") .moveSingleActivityIdToActivityIds("usertask5",newActivityId) .changeState(); }
- 第一步:ui,绘制流程图
- 子流程回退
- 应用场景
- 子流程回退,其实可以看成一个串行回退。就是一个串行的,没什么太多区别啊。
- 关注1:子任务1回退到主任务1
- 关注2:主任务2回退到子任务1
- 案例演示
- 第一步:ui,绘制流程图:
- 主任务1,user1,usertask1
- 子任务1,user2,usertask2
- 主任务2,user3,usertask3
- 第二步:演示1:子任务1回退到主任务1
- 代码实现1:
/** * 回退操作 * 从子流程回退到主流程操作 */ @Test void rollbackMainTask(){ // 回退操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId("2501") // 没有多个时,可用此方法 .moveActivityIdTo("usertask2","usertask1") .changeState(); } }
- 代码实现2:
/** * 回退操作 * 从子流程回退到主流程操作:moveExecutionToActivityId不关心当前的节点 */ @Test void rollbackMainTask(){ // 回退操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId("2501") .moveExecutionToActivityId("5003","usertask1") .changeState(); }
- 代码实现1:
- 第三步:演示2:主任务2回退到子任务1
- 代码实现:
/** * 回退操作 */ @Test void rollbackSubTask(){ // 回退操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId("2501") //当然也可以回退到usertask1 .moveActivityIdTo("usertask3","usertask2") .changeState(); }
- 代码实现:
-
第四步:验证
-
task表
-
act_hi_actinst
-
- 第一步:ui,绘制流程图:
- 应用场景
-
撤消 = 回退 + 删除历史记录信息
-
实际开发过程中,要结合历史数据。通过一些sql语句,所这些回退节点都找出来。如果说操作比较复杂,你可能还要去重、编排、处理。
-
三 动态表单
- 概述
- 应用场景:在做审批流程的时,审批流程中的要有数据流转。
- 方案1(建议):可以做一个请假的表单、审批的表单、报销的一个表单,使用表单更规范。
- 方案2(不建议):使用流程变量绑定数据,也可以,但不规范。
- 应用场景:在做审批流程的时,审批流程中的要有数据流转。
- 内置表单
- 应用场景
- 启动流程时可以给它绑定一个表单。
- 启动流程的时候,可以往表单里面去给一些默认信息。
- 用户申请节点可以打开表单,并且可以修改(录入)其中的一些信息。比如,修改(录入)请假原因、请假天数、请假开始与结束时间等。
- 案例演示
- 第一步:ui,绘制流程图,请假审批流程:
- 第1步:空启动事件
- 第a步:空启动事件-动态表单属性:请假天数 days:
- 第b步:空启动事件-动态表单属性:请假开始时间 startTime:
- 第c步:空启动事件-动态表单属性:请假原因 reson string。
- 第2步:用户申请,user1,usertask1
- 第a步:用户申请-动态表单属性:请假天数 days。
- 第b步:用户申请-动态表单属性:请假开始时间 startTime:
- 第c步:用户申请-动态表单属性:请假原因 reson string。
- 第3步:排他网关
- 总监审批,days>3,user2,usertask2
- 注意:如果这个节点,需要用表单去做相关操作,也是可以设置动态表单属性的。
- 部门经理审批,days<=3,user3,usertask3
- 注意:如果这个节点,需要用表单去做相关操作,也是可以设置动态表单属性的。
- 总监审批,days>3,user2,usertask2
- 第4步:空结束事件
- 第1步:空启动事件
- 第二步:idea编码
- 第1步:部署流程
- 第a步:编码:查看流程关联的表单信息。比如,查看空启动事件节点 或 用户申请节点有没有关联相关的表单:
/** * 查看部署的流程内置的表单信息 */ @Test public void getStartFromData(){ ProcessEngine processEngine = configuration.buildProcessEngine(); StartFormData startFormData = processEngine.getFormService().getStartFormData("inner_form:1:4"); List<FormProperty> formProperties = startFormData.getFormProperties(); for (FormProperty formProperty : formProperties) { String id = formProperty.getId(); String name = formProperty.getName(); FormType type = formProperty.getType(); System.out.println("id = " + id); System.out.println("name = " + name); System.out.println("type.getClass() = " + type.getClass()); } }
- 第b步:验证:查看流程关联的表单信息。比如,查看空启动事件节点 或 用户申请节点有没有关联相关的表单:
- 第a步:编码:查看流程关联的表单信息。比如,查看空启动事件节点 或 用户申请节点有没有关联相关的表单:
- 第2步:创建(启动)流程实例
- 第a步:编码
/** * 正常的启动流程 */ @Test void startFlow() throws Exception{ Map<String,Object> map = new HashMap<>(); map.put("days","5"); map.put("startDate","20220403"); map.put("reason","想休息下"); ProcessInstance processInstance = runtimeService .startProcessInstanceById("myProcess:5:4dd61987-b313-11ec-882d-c03c59ad2248",map); } /** * 通过FormService来启动一个表单流程 * @throws Exception */ @Test void startFormFlow() throws Exception{ ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId("4da14de4-b313-11ec-882d-c03c59ad2248") .singleResult(); // map只能是<String,String> Map<String,String> map = new HashMap<>(); map.put("days","2"); map.put("startDate","20220406"); map.put("reason","出去玩玩"); // 提交启动的表单数据 // ProcessInstance processInstance = processEngine.getFormService().submitStartFormData("inner_form:1:4", map); ProcessInstance processInstance = processEngine.getFormService().submitStartFormData(processDefinition.getId(), map); }
- 第b步:验证
- act_ru_task:有2条任务Task
- 第a步:编码
- 第3步:查看某个任务表单数据
- 第a步:编码
/** * 根据Task编号来查看表单数据 */ @Test public void getTaskFormData(){ ProcessEngine processEngine = configuration.buildProcessEngine(); String taskId = "2508";// taskId,即任务编号 TaskFormData taskFormData = processEngine.getFormService().getTaskFormData(taskId); List<FormProperty> formProperties = taskFormData.getFormProperties(); for (FormProperty formProperty : formProperties) { System.out.println("formProperty.getId() = " + formProperty.getId()); System.out.println("formProperty.getName() = " + formProperty.getName()); System.out.println("formProperty.getValue() = " + formProperty.getValue()); } }
- 第b步:验证
- 第a步:编码
- 第4步:修改(保存、更新)某个任务表单数据
- 第a步:编码
/** * 根据Task编号来修改(保存、更新)表单数据 */ @Test public void saveOrupdateTaskFormData(){ ProcessEngine processEngine = configuration.buildProcessEngine(); String taskId = "2508";// taskId,即任务编号 Map<String,String> map = new HashMap<String,String>(); map.put("reson","巴拉巴拉"); FormService formService = processEngine.getFormService(); formService.saveFormData(taskId,map); }
- 第b步:验证
- 第a步:编码
- 第5步:完成任务
- 编码:
/** * 直接完成任务 */ @Test public void submitTaskFormData1(){ // 直接完成 ProcessEngine processEngine = configuration.buildProcessEngine(); processEngine.getTaskService().complete("2508"); } /** * 保存表单数据并完成任务 */ @Test public void submitTaskFormData2(){ ProcessEngine processEngine = configuration.buildProcessEngine(); String taskId = "2508"; // 完成任务的同时可以修改(保存、更新)表单数据 Map<String,String> map = new HashMap<String,String>(); map.put("days","7"); map.put("startDate","20220408"); map.put("reason","9出去玩玩"); processEngine.getFormService().submitTaskFormData(taskId,map); }
- 验证:走上面的“ 第3步:查看某个任务表单数据 ”。
- 注意:这里为什么不出现"days","startTime","reason"这些表单数据呢?因为在绘制流程图的时候,我们没有给这个节点绑定任何的表单。我们只给“空启动事件”和“用户申请”两个节点绑定了表单。
- 编码:
- 第6步:查看历史Task表单数据:一个Task完成后,如果我们想要查看之前的表单的历史数据可以通过如下的方法来实现
/** * 查看已经完成的Task的表单数据 */ @Test void getHisTaskFormData(){ String taskId = "80efeb32-b313-11ec-a7ff-c03c59ad2248"; List<HistoricDetail> list = processEngine.getHistoryService() .createHistoricDetailQuery() .taskId(taskId) .formProperties() .list(); for (HistoricDetail historicDetail : list) { HistoricFormPropertyEntityImpl his = (HistoricFormPropertyEntityImpl) historicDetail; System.out.println("his.getPropertyId() = " + his.getPropertyId()); System.out.println("his.getPropertyValue() = " + his.getPropertyValue()); } }
- 第1步:部署流程
- 第一步:ui,绘制流程图,请假审批流程:
- 应用场景
- 内置表单的缺点
- 我们会发现在上面的例子中通过内置的表单,我们需要在每个节点都设置一份表单数据,不是很灵活,这时我们可以单独创建一份表单,然后在对应的节点做引用就可以了。
- 外围表单
- 案例演示
- 第一步:创建表单在项目中创建test.form,内容是下面的json。注意:表单定义文件是以.form为后缀, 内容格式为Json格式。注意:上面文件中的key是唯一标识,我们在表单处理的时候是根据这个key来获取的哦。
{ "key": "form1", "name": "请假流程", "fields": [ { "id": "startTime", "name": "开始时间", "type": "date", "required": true, "placeholder": "empty" }, { "id": "days", "name": "请假天数", "type": "string", "required": true, "placeholder": "empty" }, { "id": "reason", "name": "请假原因", "type": "text", "required": true, "placeholder": "empty" } ] }
- 第二步:绘制流程图(或者 修改Xxx.bpmn20.xml文件,修改相应的节点,删除内置表单配置,加入处置表单引用):
- 空启动事件、用户申请、总监审批、部门经理审批都引用上面的表单form1。
- 第三步:部署流程
- 第四步:部署表单。因为流程关联了form1表单,因此在流程部署结束后要单独对表单进行部署。
- 编码:
@Autowired private FormRepositoryService formRepositoryService; /** * 部署form表单:在外置Form表单中我们需要在流程部署完毕后,再对表单做部署的操作 */ @Test public void deployForm() throws Exception{ FormDeployment formDeployment = formRepositoryService.createDeployment() .addClasspathResource("holiday.form") .name("test") .parentDeploymentId("1") //和我们上面部署的流程关联 .deploy(); System.out.println("formDeployment.getId() = " + formDeployment.getId()); }
- 验证:act_fo_form_deployment
- 编码:
- 第五步:创建(启动)流程实例
- 编码:
/** * 启动流程实例,并且设置对应的值 */ @Test void startTask(){ Map<String,Object> map = new HashMap<>(); map.put("days","4"); map.put("startTime","20220404"); map.put("reason","出去玩玩"); ProcessInstance processInstance = runtimeService.startProcessInstanceWithForm( "myProcess:1:4" , null , map , "请假流程"); String id = processInstance.getId(); System.out.println("id = " + id); }
- 验证:act_ru_task
- 报错:NullPointException
- 原因:startTime = new Date()
- 解决:startTime = 20221118
- 编码:
- 第六步:查看某个Task对应的表单信息
- 编码:
/** * 查看流程定义表单数据 */ @Test public void getTaskFormData1(){ Task task = taskService.createTaskQuery() .processDefinitionId("myProcess:1:4") .taskAssignee("zhangsan") .singleResult(); // FormInfo 表单的元数据信息 FormInfo formInfo = runtimeService.getStartFormModel("myProcess:1:4", "5001"); System.out.println("formInfo.getId() = " + formInfo.getId()); System.out.println("formInfo.getName() = " + formInfo.getName()); System.out.println("formInfo.getKey() = " + formInfo.getKey()); // FormModel 表单中的具体信息 具体实现是 SimpleFormModel SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel(); List<FormField> fields = formModel.getFields(); for (FormField field : fields) { System.out.println("field.getId() = " + field.getId()); System.out.println("field.getName() = " + field.getName()); System.out.println("field.getValue() = " + field.getValue()); } System.out.println("formModel = " + formModel); } /** * 查看具体的Task的表单数据 */ @Test void getTaskData(){ FormInfo formInfo = taskService.getTaskFormModel("17505"); System.out.println("formInfo.getId() = " + formInfo.getId()); System.out.println("formInfo.getName() = " + formInfo.getName()); System.out.println("formInfo.getKey() = " + formInfo.getKey()); SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel(); List<FormField> fields = formModel.getFields(); for (FormField field : fields) { System.out.println("field.getId() = " + field.getId()); System.out.println("field.getName() = " + field.getName()); System.out.println("field.getValue() = " + field.getValue()); } }
- 验证:打印都一样
- 编码:
- 第七步:完成任务:在外置表单的场景中我们需要通过
taskService.completeTaskWithForm
来完成表单的任务- 编码:
/** * 完成任务 */ @Test public void completeTaskForm(){ Map<String,Object> map = new HashMap<>(); map.put("days","4"); map.put("startTime","20220404"); map.put("reason","出去玩玩"); String taskId = "5010"; String formDefinitionId = "2503"; String outcome = "波哥"; taskService.completeTaskWithForm(taskId,formDefinitionId,outcome,map); }
- 验证:act_ru_task
- 编码:
- 第一步:创建表单在项目中创建test.form,内容是下面的json。注意:表单定义文件是以.form为后缀, 内容格式为Json格式。注意:上面文件中的key是唯一标识,我们在表单处理的时候是根据这个key来获取的哦。
- 案例演示
四 事件
1 空启动事件
- 首先,部署流程。注意此时,流程实例没有创建(启动)。
- 最后,创建(启动)流程实例:需要人为显示调用startProcessInstanceByXXX API方法。
- 注意:在部署的流程中加入了定时事件、消息事件、错误事件、信号事件等等以后,创建(启动)流程实例就有了不同的情况:
- 比如,启动时不再是空启动事件。
- 比如,部署完成的流程中加入了定时事件。可能过个10分钟(定时),flowable就会自动创建(启动)新流程实例。
- 比如,部署完成的流程中注册了信号量。那么flowable能够捕获相应的信号,然后触发了信号事件,可能flowable就会自动创建(启动)新流程实例。
2 定时事件
- 应用场景
- 每天晚上的某个时候,启动定时触发的任务。然后呢,1234,通过各个流程去流转。
- 定时器启动事件
- 第一步:UI,绘制流程图
- 第1步:定时器启动事件(删除空启动事件)
- 第a步:触发的时间设置:开始时间(ISO-8601) 2022-03-27T23:14:14
- 第2步:用户任务(也可以是别的任务)
- 第a步:分配给单个用户,zhangsan
- 第3步:结束事件
- 第4步:保存
- 第1步:定时器启动事件(删除空启动事件)
- 第二步:UI,演示应用程序(部署流程)
- 第1步:创建一个应用程序
- 第a步:添加包含的流程
- 第b步:保存并退出
- 第2步:发布些应用程序
- 第1步:创建一个应用程序
- 第三步:UI,zhangsan用户登录,并验证:
- 第1步:到2022-03-27T23:14:14时会自动创建(启动)流程。通过FlowableUI我们可以看到,没有启动流程实例的情况下,到里23:14:14秒的时候自动帮助我们创建了一个流程实例。
- 第2步:并且任务流转到第1个任务节点,处理人是zhangsan。
- 可查看显示图
- 第3步:zhangsan处理任务,
- 第4步:流程结束。
- 第一步:UI,绘制流程图
- 定时器捕获中间事件
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:任务Task,人工处理,固定zhangsan
- 第3步:中间计时器捕获事件
- 第a步:触发的时间设置:开始时间(ISO-8601) 2022-03-27T23:25:14
- 第4步:任务Task,人工处理,固定lisi
- 第5步:流程结束
- 第6步:保存
- 第二步:UI,演示应用程序(部署流程)
- 第1步:创建一个应用程序
- 第a步:添加包含的流程
- 第b步:保存并退出
- 第2步:发布些应用程序
- 第1步:创建一个应用程序
- 第三步:UI,任务应用程序
- 第1步:流程Tab,启动流程(创建流程实例)
- 第2步:zhangsan
- 第a步:切换用户,使用zhangsan用户登录。
- 第b步:UI,任务应用程序,流程Tab,zhangsan点击完成”人工处理“
- 第3步:lisi
- 第a步:切换用户,使用lisi用户登录。
- 第b步:刚开始,没有任务
- 第c步:直到2022-03-27T23:25:14时,有一个任务
- 总结
- 当第一个人工处理完成后,第二个人工处理的任务需要在2022-03-27T23:25:14之后执行
- 通过FlowableUI的案例演示我们可以看到后一个任务是在定时时间之后执行的
- 第一步:UI,绘制流程图
- 定时器边界事件:比如,流程流转到某个任务Task,而这个任务Task过了多久没有被处理会怎么怎么样
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:任务Task,人工处理1,固定zhangsan
- 第3步:边界计时器事件
- 第a步:触发的时间设置:开始时间(ISO-8601)
2022-03-27T23:36:14
- 第a步:触发的时间设置:开始时间(ISO-8601)
- 第5步:任务Task,人工处理2,固定lisi
- 第4步:任务Task,人工处理3,固定wangwu
- 第6步:流程结束
- 第7步:保存
- 第二步:UI,演示应用程序(部署流程)
- 第1步:创建一个应用程序
- 第a步:添加包含的流程
- 第b步:保存并退出
- 第2步:发布些应用程序
- 第1步:创建一个应用程序
- 第三步:UI,任务应用程序
- 第1步:流程Tab,启动流程(创建流程实例)
- 第2步:zhangsan
- 第a步:切换用户,使用zhangsan用户登录。
- 第b步:UI,任务应用程序,任务Tab,zhangsan有1个任务,但他不处理任务Task
- 第3步:等到2022-03——27T23:36:14
- 第4步:wangwu
- 第a步:切换用户,使用wangwu用户登录。
- 第b步:UI,任务应用程序,任务Tab,有1个任务Task
- 总结:
- 人工任务1如果在定义的
2022-03-27T23:36:14
这个时间之前还没有处理,那么就会触发定时边界事件,从而从人工任务3。 - 然后在张三这个位置我们不完成,等到定时到来,达到定时的时间,任务进入到了人工审批三
- 人工任务1如果在定义的
- 第一步:UI,绘制流程图
- timeDuration
- 定时器启动事件:持续时间
- 第一步:UI,绘制流程图
- 第1步:定时器启动事件
- 第a步:持续时间(例: PT5M),PT2M/PT30S,部署后等待2分钟启动
- 第2步:服务任务(Task),自动任务
- 第a步:设置属性 "类",值是 "类的路径"
- 附:自动任务处理规则
- 如果没特殊的情况,自动任务会自动处理完成
- 如果想捕获自动任务的自动处理事件(行为),并做相关处理的话,可以创建一个委托的java去做处理。
- 第3步:结束流程
- 第4步:保存,导出
- 第1步:定时器启动事件
- 第二步:idea编码
- 第1步:自动任务,委托处理类
- 第2步:部署流程(可以用XxxController方式,也可以用单元测试的方式)
- 第三步:启动项目
- 第四步:浏览器访问,验证
- 2分钟后,可以看数据库
- 总结:
- 发布任务后然后我们等待两分钟就可以看到任务到了
zhangsan
的位置。 - 重复部署流程,定时任务会重复走。
- 发布任务后然后我们等待两分钟就可以看到任务到了
- 第一步:UI,绘制流程图
- 中间计时器捕获事件:持续时间
- 第一步:UI,绘制流程图
- 第1步:自动任务一(任务类型为:服务任务)(任务类型:因为异步任务)
- 类 = 处理类的路径
- 第2步:中间计时器捕获事件
- 持续时间 = 30s
- 第3步:自动任务二(任务类型为:服务任务)(任务类型:因为异步任务)
- 类 = 处理类的路径
- 第1步:自动任务一(任务类型为:服务任务)(任务类型:因为异步任务)
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例,阻塞(因为异步任务)
- 第3步:流转到”自动任务一“,并自动完成任务,类的方法执行
- 第4步:阻塞多长时间
- 第5步:流转到”自动任务二“,并自动完成任务,类的方法执行
- 总结:阻塞多长时间,才把流转到下一个任务Task
- 第一步:UI,绘制流程图
- 边界计时器事件:持续时间
- 第一步:UI,绘制流程图
- 边界计时器事件,持续时间 = 5m
- 自动任务,类 = 类的路径
- 总结:“人工审核”任务Task,在5分钟之内还没有处理,触发边界计时器事件,然后调度(自动流转到)自动任务二
- 第一步:UI,绘制流程图
- 定时器启动事件:持续时间
- timeCycle:循环时间
- 定时器启动事件:循环时间
- 第一步:UI,绘制流程图
- 定时器启动事件,循环时间 = R3/PT20s(循环次数是3次,每次间隔20s)
- 自动任务一,类 = 类路径,是一种异步任务
- 总结: 效果是,部署流程以后,会自动创建(启动)3个流程实例,间隔20s。如果不指定次数,那么会无限循环下去(每隔多长时间都做一次)。
- 第一步:UI,绘制流程图
- 中间计时器捕获事件:循环时间
- 第一步:UI,绘制流程图
- 自动任务一,类 = 类路径,是一种异步任务
- 中间计时器捕获事件,循环时间 = R3/PT20s(循环次数是3次,每次间隔20s)
- 自动任务二,类 = 类路径,是一种异步任务
- 总结:即使指定了多次循环次数,也只会触发1次中间计时器捕获事件,即只有1次流转到自动任务二。
- 第一步:UI,绘制流程图
- 边界计时器事件:循环时间
- 第一步:UI,绘制流程图
- 总结: 用户任务在指定的时间内没有被处理的话,会触发边界计时器事件(循环时间),然后任务流转到自动任务二。但是即使指定了多次循环次数,也只会触发1次边界计时器事件,即只有1次流转到自动任务二。
- 循环设定
-
启动事件:根据设置循环启动流程实例
-
边界事件:即使设置了循环时间也只会触发1次
-
中间事件:即使设置了循环时间也只会触发1次
-
- 循环设定
- 定时器启动事件:循环时间
3 消息事件
- 概述
- 消息:接收的邮件、接通电话、接收的短信等各种外部消息。
- 消息事件:接收了消息以后,触发消息事件。
- 消息启动事件
- 第一步:UI,绘制流程图
- 第1步:定义外部要发送的消息:点击空白,消息定义属性。
- 第2步:点击消息节点,选择订阅的“外部消息”。
- 第3步:自动流程处理,类 = 类路径,是一种异步任务。
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- startProcessInstanceByMessage("xxx"),发送外部消息。注意:发送消息发送的应该是消息的名称而不是消息的ID。
- 总结: 效果,通过发外部消息触发消息启动事件,即创建(启动)了1个新的流程实例。然后,任务流转到“自动流程处理”,因为此任务是1个自动任务,所以会自动完成。最后,结束流程。
- 第一步:UI,绘制流程图
- 中间消息捕获事件
- 第一步:UI,绘制流程图
- 第1步:定义外部要发送的消息:点击空白,消息定义属性。
- 第2步:点击消息节点,选择订阅的“外部消息”。
- 第二步:idea编码
- 通过单元测试来发布外部事件:runtimeService.messageEventReceived("第二个消息",processExecutionId);,来触发中间消息捕获事件。
- 总结:消息中间事件就是,在流程运作中需要消息来触发的场景。案例演示,自动流程1处理完成后,需要接收特定的消息之后才能进入到自动流程2。
- 第一步:UI,绘制流程图
- 边界消息事件
- 第一步:UI,绘制流程图
- 第1步:定义外部要发送的消息:点击空白,消息定义属性。
- 第2步:点击消息节点,选择订阅的“外部消息”。
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例,流转到任务Task“人工处理”
- 第3步:单元测试,触发边界事件:runtimeService.messageEventReceived("第三个消息",processExecutionId);
- 总结:如果人工处理在消息订阅前没有处理就会触发边界事件
- 第一步:UI,绘制流程图
4 错误事件
- 概述
- 应用场景:当流程中抛出相关错误时,就会触发错误事件。
- 错误事件,只有开始错误事件和边界错误事件,而没有中间错误事件。
- 异常启动事件
- 注意事项
- 注意1:错误启动事件不能用于启动流程实例。即开始错误事件,不能够做为整个流程的启动事件。
- 注意2:错误启动事件(error start event),可用于触发事件子流程(Event Sub-Process)。
- 注意3:错误启动事件总是中断。
- 第一步:UI,绘制流程图
- 第1步:主流程:
- 自动任务1,类 = 类路径
- 第2步:事件子流程
- 第a步:异常启动事件
- 属性设置:错误引用 = error01
- 第b步:自动任务2 ,类 = 类路径
- 第a步:异常启动事件
- 第1步:主流程:
- 第二步:idea编码
- 第1步:修改Xxx.bpmn.xml文件,进行错误定义:
- 第2步:自动任务一的处理类抛出异常:
public class MyOneDelegate implements JavaDelegate { @Override public void execute(DelegateExecution execution) { System.out.println("完成自动审批任务-----》MyOneDelegate" + LocalDateTime.now().toString()); // 业务执行发现有问题 此处的errorCode需要和定义的error标签中的errorCode保持一致 throw new BpmnError("abcd"); } }
- 总结:首先,主流程一启动,流转到“自动任务一”。其次,如果“自动任务一”抛出了1个异常,那么事件子流程中的异常启动事件节点就会捕获到错误事件。最后,流程就会流转到事件子流程中的“自动任务二”。
- 注意事项
- 边界错误事件
- 注意事项
- 边界错误事件,是在边界上面去触发。
- 边界错误事件,使用的是子流程,而不是事件子流程。
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:子流程
- 第a步:空启动事件
- 第b步:自动任务一,类别是服务任务,类 = 类路径
- 第c步:空结束事件
- 第3步:自动任务二,类 = 类路径
- 第4步:边界错误事件
- 属性设置:错误引用:a123
- 第5步:自动任务三,类 = 类路径
- 第二步:idea编码
- 第1步:修改Xxx.bpmn.xml文件,进行错误定义:
- 第2步:部署流程
- 第3步:创建(启动)流程实例
- 总结:当流程开始,流转到子流程中来。如果子流程中的自动任务抛出指定的异常,那么就会触发边界错误事件的捕获,流程就会流转到自动任务三。
- 注意事项
5 信号事件
- 应用场景
- 因为某些政策的变动,会自动发起一个人工审批的流程。
- 信号启动事件
- 全局
- 第一步:UI,绘制流程图
- 第1步:信号启动事件
- 信号引用 = signal01
- 第2步:自动任务
- 第3步:空结束事件
- 第4步:点击空白处,信号定义
- 第1步:信号启动事件
- 第二步:idea编码
- 全局
- 第1步:修改Xxx.bpmn.xml文件,进行错误定义:
- flowable:scope="global",全局
- 第2步:部署流程
- 第3步:流程是一个信息启动事件,所以我们需要释放对应的信号来触发流程的启动
/** * 通过信号发送来触发信号启动事件的执行 * 全局的信息 */ @Test void signalReceived() throws Exception { runtimeService.signalEventReceived("firstSignal"); // 我们得保证容器的运行,所以需要阻塞 TimeUnit.MINUTES.sleep(1); }
- 第1步:修改Xxx.bpmn.xml文件,进行错误定义:
- 全局
- 第一步:UI,绘制流程图
- 局部
- flowable:scope="processInstance",局部
- 注意:ps:信号启动事件中,使用全局或者是局部都没有区别,甚至说不起作用了。我们可以把信息的作用域由原来的golbal全局的调整为processInstance,测试后发现还是执行了,说明在启动事件信息的作用域其实是不起作用的
- 总结:
- 首先,定义一个任务。任务会去接收一个信号,做为该任务触发的条件。
- 效果:发出信号,触发信号启动事件,流程会流转到自动任务。
- 全局
- 中间信号捕获事件
- 全局
- 第一步:UI,绘制流程图
- 第1步:信号定义,signal02
- 第2步:空启动事件
- 信号引用 = signal02
- 第3步:中间信号捕获事件
- 第4步:自动任务,类 = 类路径
- 第5步:空结束事件
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 第3步:流程阻塞
- 第4步:发出全局信号
/** * 通过信号发送来触发信号启动事件的执行 * 全局的信息 */ @Test void signalGolbal() throws Exception { runtimeService.signalEventReceived("secondSingal"); // 我们得保证容器的运行,所以需要阻塞 TimeUnit.MINUTES.sleep(1); }
- 第一步:UI,绘制流程图
- 局部:信号作用域为processInstance的情况
- 第一步:xml文件修改,信号作用域为processInstance
- 第二步:部署流程
- 第三步:创建(启动)流程实例
- 第四步:
- 仍然发全局的信号,控制台没有输出,事件不触发。
- 发出局部信号,不处理。因为processInstance的信息我们需要在流程实例内部抛出信号:
- 总结:
- 当我们启动事件后,会阻塞在这个消息获取中间事件处,等待相关信号后才会继续流转。
- 定义获取processInstance的信号,如果我们发送的是Global信号,那么是不会捕获的。
- 全局
- 中间信号抛出事件
- 第一步:UI,绘制流程图
- 第1步:信号定义,全局,signal03
- 第2步:流程启动
- 第3步:并行网关 —— 分支
- 第4步:并行方向1
- 自动任务一
- 中间信号抛出事件,即抛出signal03信号
- 信号引用 = signal03
- 自动任务二
- 第5步:并行方向2
- 中间信号捕获事件,即捕获signal03信号
- 信号引用 = signal03
- 自动任务三
- 中间信号捕获事件,即捕获signal03信号
- 第6步:并行网关 —— 汇集
- 第7步:流程结束
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 总结:
- 并行方向1:执行到“中间信号抛出事件”时,会抛出一个信号。抛出信号后,流转到“自动任务二”。
- 并行方向2:执行到“中间信号抛出事件”时,会抛出一个信号。信号触发“中间信号捕获事件”,流转到“自动任务三”。
- 第一步:UI,绘制流程图
- 边界信号事件
- 第一步:UI,绘制流程图
- 第1步:信号定义,signal04
- 第2步:空启动事件
- 第3步:人工任务,zhangsan
- 第4步:自动任务1,类 = 类路径
- 第5步:边界信号事件
- 信号引用 = signal04
- 第6步:自动任务2,类 = 类路径
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 第3步:抛出信号:
@Test public void signalGlobal() throws Exception{ String signal = "signal2"; Map<String, Object> variables = new HashMap(); processEngine.getRuntimeService().signalEventReceived(signal,variables); }
- 第4步:流转到“自动任务二”
- 第5步:zhangsan处理任务
- 第6步:流转到“自动任务一”
- 第一步:UI,绘制流程图
6 结束事件
- 错误结束事件
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:子流程
- 第a步:空启动事件
- 第b步:自动任务一,类 = 类路径
- 第c步:排他网关
- 第d步:分支1:流条件 flag > 0 》空结束事件
- 第e步:分支2:流条件 flag <= 0 》错误结束事件,错误引用 = error03
- 第3步:自动任务二,类 = 类路径 》空结束事件
- 第4步:错误边界事件,错误引用 = error03 》自动任务三,类 = 类路径 》空结束事件
- 第二步:idea编码
- 第1步:错误定义,修改bpmn.xml文件:
- 第2步:部署流程
- 第3步:创建(启动)流程实例
- 第a步:绑定全局变量:flag = 0
- 总结:
- 如果经过排他网关后,flag>0,走到的是空结束事件,那么就正常地走自动任务二 》空结束事件。
- 反之,如果,flag<=0,流转到错误结束事件后,会抛出1个错误,如果没有地方捕获的话整个流程就在异常状态下结束。
- 如果错误边界事件捕获了错误结束事件抛出的错误(相当于catch处理),那就走自动任务三。
- 第一步:UI,绘制流程图
- 结束终止(中断)事件
- 案例1:无子流程
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:并行网关
- 第a步:用户任务一,zhangsan
- 第b步:用户任务二,lisi
- 第c步:用户任务三,wangwu
- 排他网关
- 分支1:流条件(连接条件)&{flag <= 0} 》终止结束事件
- 分支2:流条件(连接条件)&{flag > 0} 》汇集并行网关
- 排他网关
- 第3步:并行网关
- 第4步:空结束事件
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 第3步:wangwu处理,flag = 0
- 总结:
- 条件,在没有子流程的情况下:
- 如果“终止结束事件”被触发,则整个流程结束(即并行网关的3条分支都会结束)。
- 比如,总结理觉得靠近双十一会很忙,在对于张三的请假申请(并行的人事审批、行政审批、总经理审批)直接终止。
- 效果:wangwu处理,flag = 0,触发终止结束事件,Task表中删除3条分支的任务数据。查看历史数据act_hi_actinst表,发现被删除的3条中的2条有终止结束事件标识。
- 条件,在没有子流程的情况下:
- 第一步:UI,绘制流程图
- 案例2:有子流程
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:并行网关
- 第a步:用户任务一,zhangsan
- 第b步:用户任务二,lisi
- 第c步:子流程
- 第i步:空启动事件
- 第ii步:子人工任务一,wangwu
- 第iii步:排他网关
- 分支1:子人工任务二,赵六
- 分支2:终止结束事件
- 第iiii步:空结束事件
- 第3步:并行网关
- 第4步:空结束事件
- 第二步:idea编码
- 第1步:部署、创新(启动)
- 第2步:子人工任务一,wangwu,处理,flag = 0
- 总结:
- 效果:子流程中的终止事件被触发,那么子流程肯定会结束。但主流程中的另外两条分支(用户任务一、用户任务二),还可以继续执行。
- 第一步:UI,绘制流程图
- 案例1:无子流程
- 空结束事件,最开始就用的
- 结束取消事件
- 第一步:UI,绘制流程图
- 第1步:空启动事件
- 第2步:事务子流程
- 第a步:空启动事件
- 第b步:人工任务一,zhangsan
- 第c步:补偿边界事件 》补偿自动任务,类 = 类路径,Is for compensations(支持补偿)
- 第d步:排他网关
- 第i步:分支1:人式任务二,lisi,${flag > 0 } 》空结束事件
- 第ii步:分支2:${flag <= 0 } ,结束取消事件
- 正常结束任务
- 取消边界事件 》取消事件结束(类型:自动任务,类 = 类路径)
- 第二步:idea编码
- 第1步:部署流程
- 第2步:创建(启动)流程实例
- 第a步:绑定流程变量 flag = 0。
- 第3步:人工任务一,zhangsan,处理
- 总结:
- 效果:
- 首先,流转到“结束取消事件”,抛出取消结束事件。
- 然后,”取消边界事件“捕获,执行”取消事件结束(类型:自动任务,类 = 类路径)“
- 最后,触发“补偿边界事件”,执行“补偿自动任务”。
- 所以,在这个流程中:”补偿自动任务“、”取消事件结束“会被执行,而”正常结束“不会被执行。
- 效果:
- 注意事项:
- 结束取消事件我们只能在事务子流程中使用。
- 如果不在事务子流程中使用,那么就会抛出异常。
- 在FlowableUI中暂时没有找到这个组件,所以在Eclipse中来绘制。
- “补偿边界事件”是人”人工任务一“执行完毕之后,才会被触发。然后,以前我们学的”Xxx边界事件”是在“人工任务一“还没正常地走的情况下,并且条件满足的情况下,才会被抛出、捕获、处理。
- 第一步:UI,绘制流程图
- 补偿事件:补偿抛出中间事件、补偿边界事件
- 概述
- 补偿抛出中间事件
- 用于抛出补偿事件。
- 重点:抛出补偿时,当前范围中所有的活动,包括并行分支上的活动都会被补偿。
- 补偿边界事件
- 用于:捕获补偿事件
- 重点:这里的“边界补偿事件”和上面的几种“边界xxx事件”的区别是:“边界xxx事件”,是在当前的任务Task还没有被处理的情况下,满足某种条件而触发的边界事件。而“边界补偿事件”是在其依附的活动成功完成时激活。
- 补偿抛出中间事件
- 第一步:UI,绘制流程图:
- 第1步:空启动事件
- 第2步:并行网关-分支
- 第a步:分支1:预订机票-自动,类 = 类路径
- 第i步:补偿边界事件
- 第ii步:取消预订-补偿器,Is for compensations(支持补偿、可补偿的),类 = 类路径
- 第i步:补偿边界事件
- 第b步:分支2:微信支付-自动,类 = 类路径
- 第i步:边界错误事件
- 第ii步:补偿抛出中间事件
- 第iii步:空结束事件
- 第ii步:补偿抛出中间事件
- 第i步:边界错误事件
- 第a步:分支1:预订机票-自动,类 = 类路径
- 第3步:并行网关-汇集
- 第4步:空结束事件
- 第二步:idea编码
- 第1步:错误定义,修改Xxx.bpmn.xml文件:
- 第2步:部署流程
- 第3步:创建(启动)流程实例
- 第4步:在微信支付-自动,类 = 类路径,抛出错误:
public class MyOneDelegate implements JavaDelegate { @Override public void execute(DelegateExecution execution) { System.out.println("微信支付-----》MyOneDelegate" + LocalDateTime.now().toString()); System.out.println("余额不足...."); throw new BpmnError("payFail"); } }
- 总结:
- 首先,如果微信支付-自动,付款失败,产生“错误”。
- 其次,“边界错误事件”捕获。
- 接着,流转到“补偿抛出中间事件”,产生“补偿”。
- 最后,并行分支上的“补偿边界事件”捕获补偿,执行补偿器“取消预订”
- 概述
- 结束升级事件
五 springboot+flowable ui+mysql
1 flowable-ui项目概述
(1)先看效果
- 第一步:创建springboot程序
- 第二步:springboot+flowable ui
- 第三步:springboot+flowable ui+mysql
- 第四步:启动程序
- 第1步:在mysql数据库中创建flowable ui相关的表
- 第五步:验证
- 第1步:浏览器:localhost:8082 admin test
- 第2步:各个应用程序的作用
- 建模器应用程序
- 绘制流程图
- 任务应用程序
- 部署流程
- 创建(启动)流程实例
- 管理员应用模式
- 身份管理应用程序
- 建模器应用程序
- 第六步:案例演示
- 第1步:建模器应用程序 - 绘制流程图
- 第i步:分配候选人
- 第ii步:保存和关闭编辑器
- 第2步:建模器应用程序 - 应用程序,用于创建一个应用程序
- 第i步:包含上一步绘制的流程(图)
- 第ii步:保存和关闭编辑器
- 第iii步:分布应用程序
- 第3步:任务应用程序-流程
- 第i步:添加上一步的发布(部署)的流程
- 第ii步:点击“启动流程”
- 第iii步:显示图,看绘制的流程图,和流程当前的进展
- 第4步:任务应用程序-任务
- 第i步:选择上一步启动流程的任务
- 第ii步:点击“完成”
- 第5步:验证
- act_re_deplayment
- act_hi_actinst
- act_ru_task
- 第1步:建模器应用程序 - 绘制流程图
(2)flowable ui源码下载和run
- 第一步:github官网:Release Flowable 6.7.2 release · flowable/flowable-engine · GitHub
- 也可以在官网上找下载入口:Flowable: Low-code | Process Automation | Workflow | BPM CMMN | Open Source,learn 》Open Source Community 》填写相关信息 》注册 》还是跳转到github上面来下载
- 第二步:下载源码
- 第三步:解压文件:Source code(zip)
- modules文件夹(包含很多模块)
- flowable-ui文件夹(模块)
- xxx-springboot-xxx:与springboot整合的
- xxx-admin-xxx:admin模块
- xxx-idm-xxx:idm模块
- xxx-modules-xxx:modules模块
- xxx-task-xxx:task模块
- flowable-ui文件夹(模块)
- modules文件夹(包含很多模块)
- 第四步:使用idea打开flowable-ui文件夹(模块):flowable-ui模块本身就是一个maven项目(从源码中包含pom.xml文件可以看出)。
- 第1步:把flowable-ui文件夹(模块)拷贝到硬盘中
- 第2步:使用idea打开flowable-ui文件夹(模块)
- 第3步:idea会自动编译此flowable-ui项目
- 注意:
- 以前的版本,4大模块分成4个war包,整合难度大。最新版本,4个模块合在一起,整合难度小。
- flowable-ui模块本身就是一个maven项目(从源码中包含pom.xml文件可以看出)。
(3)flowable-ui源码介绍
- 项目概述
- flowable-ui是一个集合项目
- 项目结构:包含的模块
- war项目(war包),只有一个
- flowable-ui-app:要重点关注的子项目
- jar项目(jar包)
- 剩下的都是jar项目(jar包)。
- jar项目属于公共模块,因为jar项目中的类,会被war项目中的代码所使用。
- war项目(war包),只有一个
- 项目环境
- 默认使用的数据库:h2
- 详细使用步骤
- 第一步:源代码下载
- 第二步:导入idea
- 第三步:本地启动
- 第一步:启动
- org.flowable.ui.application.FlowableUiApplication
- 第二步:访问
- localhost:8080/flowable-ui admin test
- 第三步:操作
- 绘制流程图
- 第一步:启动
- 扩展
- 可以改动其中的任何一个页面
- 如隐藏掉:任务应用程序 或 建模器应用程序 或 管理员应用程序 或身份管理应用程序。
- 修改使用的数据库:\resources\flowable-default.properties
- 修改项目名称:\resources\flowable-default.properties
- 修改项目端口:\resources\flowable-default.properties
- 修改登录用户名和密码:\resources\flowable-default.properties
- 可以改动其中的任何一个页面
- 项目结构:包含的模块
- flowable-ui是一个集合项目
- 你把这个源码弄过来了,怎么跟我们的实际工作环境做一个整合呢?即在开发过程中跟springboot做一个整合。其实很简单:
- 方案1:项目干净(只显示flowable-ui模块)
- 第一步:把war项目,flowable-ui单独拿出来看成一个普通的springboot项目(本身就是)。
- 第二步:因为剩下的都是jar项目,所以把剩下的项目都编译后打成jar包。
- 第三步:在flowable-ui项目中添加上面生成的所有jar包。
- 方案2:项目臃肿(显示10几个模块)
- 第一步:上面的结构不动
- 第二步:直接在上面的结构中做业务开发
- 方案3:自动化
- 第一步:自定义starter
- 第二步:把war项目,flowable-ui单独拿出来看成一个普通的springboot项目(本身就是)。
- 第三步:引入自定义starter,自动化地引入所有jar项目所生成的jar包。
- 方案1:项目干净(只显示flowable-ui模块)
2 springboot+flowable ui(流程设计器)+mysql
- springboot+flowable ui(流程设计器)
- 第一步:创建普通的springboot项目
- 第二步:把源码项目flowable-ui-app的pom.xml中的所有<parent>、
<dependencies>、<build>、<profiles>拉过去- 第1步:编译看看
- 第2步:版本报错: cannot resolve org.flowable:flowable-spring-boot-starter-ui-task:0.0.1
- 第a步:解决方案:统一版本<version>6.7.2</version>
- 第3步:pom.xml会红色(报错)
- <plugins>中有红色:这些都是用不到插件,可去掉
- 效果:<plugins>中的全去掉
- <pluginManagement>中有红色:这些都是用不到插件,可去掉
- <pluginManagement>中的全去掉
- <plugins>中有红色:这些都是用不到插件,可去掉
- 第4步:编译通过
- 第三步:把源码项目flowable-ui-app的resource目录下的所有文件都拉过去
- 第四步:把源码项目flowable-ui-app的java目录下的所有类拉过去
- 第1步:使用新项目的启动类,扫描注解@ComponentScan(basePackages={"org.flowable.ui.application"})
- 第2步:拷贝过来的启动类去掉main(),修改类注解为配置类注解@Configuration
- 第五步:启动
- 端口占用:修改配置文件flowable-default.properties
- 第六步:访问 localhost:8082/flowable-ui admin test,OK
- 整合mysql
- 第一步:加入mysql 8依赖
- 第二步:修改flowable-default.properties中的数据库4要素
- 注意:url要加入参数nullCatalogMeansCurrent=true
- 第三步:启动,成功
- 第1步:自动在数据库中创建n张表,并把登录的用户名和密码(admin test)写入数据库用户表中
- 第四步:访问,成功
- 第五步:操作
- 创建流程
- 测试流程